如何中断ServerSocket的accept()方法?

163

在我的主线程中,有一个while(listening)循环,它在我的ServerSocket对象上调用accept(),然后启动一个新的客户端线程并将其添加到集合中,当接受到新客户端时。

我还有一个Admin线程,我想用它来发出命令,比如'exit',这将导致所有客户端线程被关闭,关闭自己,并关闭主线程,通过将listening变量设置为false。

然而,在while(listening)循环中的accept()调用会阻塞程序运行,似乎没有任何方法可以中断它,因此无法再次检查while条件,程序也无法退出!

有更好的方法吗?或者有什么方法可以中断这个阻塞的方法吗?


1
请参见 https://dev59.com/c03Sa4cB1Zd3GeqPxbNH。 - RedPandaCurios
对于想要等待 x 时间然后再执行反应的人,可以使用 setSoTimeout()。 - Abdullah Orabi
9个回答

177

2
谢谢,好明显,我甚至都没有想到!我是在退出循环后调用close()。 - lukeo05
4
奇怪,文档中没有这个信息:http://download.oracle.com/javase/6/docs/api/java/net/ServerSocket.html#accept%28%29 方法没有标注抛出 SocketException 异常。只有在这里提到了 http://download.oracle.com/javase/1.4.2/docs/api/java/net/ServerSocket.html#close%28%29。 - Vladislav Rastrusny
1
调用 close() 方法是否可行?我的意思是,这样做会抛出异常,那么有没有其他方法(既不会抛出异常,也不基于超时)来停止监听请求呢? - Kushal
3
如果连接已经建立,线程正在等待一些数据,该怎么办:while((s=in.readLine()) != null)? - Alex Fedulov
1
@AlexFedulov 关闭该输入套接字。然后readLine()将返回null,并且线程应该已经采取的正常关闭操作将发生。 - user207421
显示剩余3条评论

33

accept()方法上设置超时时间,这样在指定的时间后会使阻塞操作超时:

http://docs.oracle.com/javase/7/docs/api/java/net/SocketOptions.html#SO_TIMEOUT

在阻塞Socket操作上设置超时时间:

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

在进入阻塞操作之前必须设置选项才能生效。如果超时时间过期且操作会继续阻塞,则会引发java.io.InterruptedIOException异常。在这种情况下,Socket不会关闭。


11

4
您可以创建一个“void”套接字来打破serversocket.accept()。 服务器端
private static final byte END_WAITING = 66;
private static final byte CONNECT_REQUEST = 1;

while (true) {
      Socket clientSock = serverSocket.accept();
      int code = clientSock.getInputStream().read();
      if (code == END_WAITING
           /*&& clientSock.getInetAddress().getHostAddress().equals(myIp)*/) {
             // End waiting clients code detected
             break;
       } else if (code == CONNECT_REQUEST) { // other action
           // ...
       }
  }

打破服务器循环的方法

void acceptClients() {
     try {
          Socket s = new Socket(myIp, PORT);
          s.getOutputStream().write(END_WAITING);
          s.getOutputStream().flush();
          s.close();
     } catch (IOException e) {
     }
}

3
< p > ServerSocket.close() 抛出异常的原因是您在该套接字上附加了< code > outputstream 或 < code > inputstream 。 您可以通过先关闭输入和输出流来安全地避免此异常。 然后尝试关闭< code > ServerSocket 。以下是一个示例:

void closeServer() throws IOException {
  try {
    if (outputstream != null)
      outputstream.close();
    if (inputstream != null)
      inputstream.close();
  } catch (IOException e1) {
    e1.printStackTrace();
  }
  if (!serversock.isClosed())
    serversock.close();
  }
}

您可以调用此方法在任何地方关闭套接字而不会出现异常。

9
输入和输出流不是连接到ServerSocket,而是连接到一个Socket上。我们谈论关闭ServerSocket而不是Socket,因此可以关闭ServerSocket而不必关闭Socket的流。 - icza

1

好的,我以一种更直接回答OP问题的方式使这个工作起来了。

请继续阅读下面的线程示例,了解我如何使用它。

简短回答:

ServerSocket myServer;
Socket clientSocket;

  try {    
      myServer = new ServerSocket(port)
      myServer.setSoTimeout(2000); 
      //YOU MUST DO THIS ANYTIME TO ASSIGN new ServerSocket() to myServer‼!
      clientSocket = myServer.accept();
      //In this case, after 2 seconds the below interruption will be thrown
  }

  catch (java.io.InterruptedIOException e) {
      /*  This is where you handle the timeout. THIS WILL NOT stop
      the running of your code unless you issue a break; so you
      can do whatever you need to do here to handle whatever you
      want to happen when the timeout occurs.
      */
}

现实世界的例子:

在这个例子中,我有一个ServerSocket在一个线程内等待连接。当我关闭应用程序时,我想要以一种干净的方式关闭线程(更具体地说是socket),所以我在ServerSocket上使用.setSoTimeout(),然后使用抛出的中断来检查父项是否正在尝试关闭线程。如果是这样,那么我将关闭socket,然后设置一个指示线程已完成的标志,然后退出线程循环,返回null。

package MyServer;

import javafx.concurrent.Task;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import javafx.concurrent.Task;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

public class Server {

public Server (int port) {this.port = port;}

private boolean      threadDone        = false;
private boolean      threadInterrupted = false;
private boolean      threadRunning     = false;
private ServerSocket myServer          = null;
private Socket       clientSocket      = null;
private Thread       serverThread      = null;;
private int          port;
private static final int SO_TIMEOUT    = 5000; //5 seconds

public void startServer() {
    if (!threadRunning) {
        serverThread = new Thread(thisServerTask);
        serverThread.setDaemon(true);
        serverThread.start();
    }
}

public void stopServer() {
    if (threadRunning) {
        threadInterrupted = true;
        while (!threadDone) {
            //We are just waiting for the timeout to exception happen
        }
        if (threadDone) {threadRunning = false;}
    }
}

public boolean isRunning() {return threadRunning;}


private Task<Void> thisServerTask = new Task <Void>() {
    @Override public Void call() throws InterruptedException {

        threadRunning = true;
        try {
            myServer = new ServerSocket(port);
            myServer.setSoTimeout(SO_TIMEOUT);
            clientSocket = new Socket();
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(true) {
            try {
                clientSocket = myServer.accept();
            }
            catch (java.io.InterruptedIOException e) {
                if (threadInterrupted) {
                    try { clientSocket.close(); } //This is the clean exit I'm after.
                    catch (IOException e1) { e1.printStackTrace(); }
                    threadDone = true;
                    break;
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
};

}

然后,在我的控制器类中...(我只会展示相关代码,按照需要将其融入你自己的代码中)
public class Controller {

    Server server = null;
    private static final int port = 10000;

    private void stopTheServer() {
        server.stopServer();
        while (server.isRunning() {
        //We just wait for the server service to stop.
        }
    }

    @FXML private void initialize() {
        Platform.runLater(()-> {
            server = new Server(port);
            server.startServer();
            Stage stage = (Stage) serverStatusLabel.getScene().getWindow();
            stage.setOnCloseRequest(event->stopTheServer());
        });
    }

}

我希望这能帮助到未来的某个人。


1

这个不起作用...如果你让它工作了,能否请你展示一个可行的代码示例? - Michael Sims

0

另一件你可以尝试的更加干净的方法是,在接受循环中检查一个标志,当你的管理员线程想要杀死阻塞在接受上的线程时,设置该标志(使其线程安全),然后建立一个客户端套接字连接到监听套接字。 接受将停止阻塞并返回新的套接字。 你可以设计一些简单的协议来告诉监听线程以清洁的方式退出线程。 然后在客户端关闭套接字。 没有异常,更加干净。


这没有任何意义...当你有像这样的代码 socket = serverSocket.accept(); 的时候,阻塞就开始了,而循环不是我们代码的一部分,所以没有办法让阻塞代码查找我设置的标志...至少我还没有找到一种方法来做到这一点...如果你有可行的代码,请分享一下? - Michael Sims
是的,看起来我遗漏了一个关键的信息,这让它不太合理。您需要使接受 socket 成为非阻塞的,并仅在一段时间内阻塞,然后循环重复,在该区域中检查退出标志。 - stu
你如何确切地将接受套接字设置为非阻塞? - Michael Sims
long on = 1L;if (ioctl(socket, (int)FIONBIO, (char *)&on)) - stu
@stu 这个问题是关于Java的套接字。 - Kröw

-1

在调用accept函数时,您可以将超时限制(毫秒)作为参数简单传递。

例如:serverSocket.accept(1000); 1秒后自动关闭请求


accept()没有参数。 - cesargastonec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接