在Java中检查ClientSocket是否已断开连接会卡住。

4
这是对一个问题的跟进:

链接

。基本上,我有一个服务器循环来管理与一个客户端的连接。在循环的某个点上,如果存在一个ClientSocket,则尝试进行读取以检查客户端是否仍然连接:
if (bufferedReader.read() == -1) {
    logger.info("CONNECTION TERMINATED!");
    clientSocket.close();
    setUpSocket(); // sets up the server to reconnect to the client
} else {
    sendHeartBeat(); // Send a heartbeat to the client
}

问题在于,一旦创建了套接字,应用程序将在读取时挂起,我认为是等待永远不会到来的数据,因为客户端从未发送到服务器。 以前这没问题,因为这样正确地处理了断开连接(当客户端断开连接时,读取最终会失败),循环将尝试重新建立连接。 然而,现在我已经添加了上面的sendHeartBeat()方法,它定期让客户端知道服务器仍然在运行。 如果读取正在占用线程,则心跳永远不会发生!
因此,我认为我正在错误地测试连接是否仍然存在。 作为快速修补措施,我可以在单独的线程中运行bufferedReader.read(),但是那样我将遇到各种并发问题,我真的不想处理。
所以问题有几个方面: 1. 我是否正确检查客户端断开连接? 2. 如果不是,我该怎么做? 3. 如果我正在正确执行操作,如何使读取不再托管进程? 或者线程是唯一的方法吗?
3个回答

9
创建套接字时,请先设置超时时间:
private int timeout    = 10000;
private int maxTimeout = 25000;

clientSocket.setSoTimeout(timeout);

这样,如果读取超时,你将会得到java.net.SocketTimeoutException(你需要捕获它)。因此,你可以像下面这样做,假设你之前已经设置了如上所示的SO_TIMEOUT,并且假设心跳始终能够从远程系统得到响应:

volatile long lastReadTime;

try {
    bufferedReader.read();
    lastReadTime = System.currentTimeMillis();
} catch (SocketTimeoutException e) {
    if (!isConnectionAlive()) {
        logger.info("CONNECTION TERMINATED!");
        clientSocket.close(); 
        setUpSocket(); //sets up the server to reconnect to the client
    } else {
        sendHeartBeat(); //Send a heartbeat to the client
    }
}

public boolean isConnectionAlive() {
    return System.currentTimeMillis() - lastReadTime < maxTimeout;
}

一种常见的处理方法是将超时时间设置为某个数字(比如10秒),然后跟踪上一次成功从套接字中读取数据的时间。如果经过了超时时间的2.5倍,那么放弃客户端并关闭套接字(这样可以发送FIN数据包到另一侧)。
如果心跳不会从远程系统获得任何响应,而只是在连接断开之前生成一个IOException的方法,那么你可以这样做(假设sendHeartBeat本身不会抛出IOException):
try {
    if (bufferedReader.read() == -1) {
        logger.info("CONNECTION TERMINATED with EOF!");
        resetConnection();
    }
} catch (SocketTimeoutException e) {
    // This just means our read timed out ... the socket is still good
    sendHeartBeat(); //Send a heartbeat to the client
} catch (IOException e) {
    logger.info("CONNECTION TERMINATED with Exception " + e.getMessage());
    resetConnection();
}

....

private void resetConnection() {
    clientSocket.close(); 
    setUpSocket(); //sets up the server to reconnect to the client
}

但是如果客户端存在,读取操作不会返回-1,对吗? - Alex
实际上,如果在时间> maxTimeout之后仍然没有发送任何内容,但客户端仍然连接,则连接可能仍然存在于您的代码中。 - Alex
但在这种情况下,您将发送两个心跳而没有响应,这意味着远程端不再通信。 - Eddie
客户端根本没有心跳反馈,即使有,也可能由于许多原因而出现长时间的“滞后”。 - Alex

1

你正在进行正确的检查,如果发生IOException,你应该添加try catch。

有一种避免线程的方法,你可以使用带有非阻塞套接字的选择器。

public void initialize(){
  //create selector
  Selector selector = Selector.open();
  ServerSocketChannel acceptSocket = ServerSocketChannel.open();
  acceptSocket.configureBlocking(false);
  String bindIp = "127.0.0.1";
  int bindPort = 80;
  acceptSocket.socket().bind(new InetSocketAddress(bindIp, bindPort));
  //register socket in selector for ACCEPT operation
  acceptSocket.register(selector, SelectionKey.OP_ACCEPT);
  this.selector = selector;
  this.serverSocketChannel = serverSocketChannel;
}

public void serverStuff() {
   selector.select(maxMillisecondsToWait);
   Set<SelectionKey> selectedKeys = selector.selectedKeys();
   if( selectedKeys.size() > 0 )
   {
      if( key.isAcceptable() ){
        //you can accept a new connection
        SocketChannel clientSk = serverSocketChannel.accept();
        clientSk.configureBlocking(false);
        //register your SocketChannel in the selector for READ operations
    clientSk.register(selector, SelectionKey.OP_READ);
      } else if( key.isReadable() ){
        //you can read from your socket.
        //it will return you -1 if the connection has been closed
      }
   }

   if( shouldSendHeartBeat() ){
     SendHeartBeat
   }
}

0

在您的断开连接检测中,应添加错误检查。有时当与另一端的连接丢失时,可能会抛出IOException异常。

恐怕这里无法避免使用线程。如果您不想阻塞代码的执行,需要创建一个单独的线程。


那你的意思是我需要在一个单独的线程中运行bufferedReader.run(),是吗? - Alex
迟早你会需要使用线程。最好在单独的线程中输出bufferedReader.read。事实上,所有套接字通信都应该在自己的线程中进行。 - kgiannakakis

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