检测插座断开?

时间:2012-12-23 10:21:51

标签: java sockets nio

我有点不高兴的是,在尝试了几个SO问题的答案中提到的不同解决方案(thisthis和其他几个)之后,这无法以优雅的方式处理,我仍然无法管理检测插座断开(通过拔下电缆)。

我正在使用NIO非阻塞套接字,除了我发现无法检测到服务器断开连接外,一切都运行正常。

我有以下代码:

while (true) {
    handlePendingChanges();

    int selectedNum = selector.select(3000);
    if (selectedNum > 0) {
        SelectionKey key = null;
        try {
            Iterator<SelectionKey> keyIterator = selector.selelctedKeys().iterator();
            while (keyIterator.hasNext()) {
                key = keyIterator.next();
                if (!key.isValid())
                    continue;

                System.out.println("key state: " + key.isReadable() + ", " + key.isWritable());

                if (key.isConnectable()) {
                    finishConnection(key);
                } else if (key.isReadable()) {
                    onRead(key);
                } else if (key.isWritable()) {
                    onWrite(key);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("I am happy that I can catch some errors.");
        } finally {
            selector.selectedKeys().clear();
        }
    }
}

正在读取SocketChannels时,我拔掉了电缆,Selector.select()开始旋转并返回0,现在我没有机会渠道,因为主要阅读&amp;编写代码由if (selectedNum > 0)保护,现在这是我头脑中的第一个混乱,来自this answer,据说当频道被破坏时,select()将返回,并且通道的选择键将指示可读/可写,但显然不是这里的情况,未选择键,select()仍返回0.

此外,从EJP's answer到类似的问题:

  

如果对等方关闭套接字:

     
      
  • read()返回-1
  •   
  • readLine()返回null
  •   
  • readXXX()为任何其他X抛出EOFException。
  •   

此处也不是这样,我尝试评论if (selectedNum > 0)并使用selector.keys().iterator()获取所有密钥,无论是否选中它们,从这些键读取不返回-1(0返回相反),写入这些键不会被EOFException抛出。我只注意到一件事,即使没有选择键,key.isReadable()返回true而key.isWritable()返回false(我想这可能是因为我没有注册OP_WRITE的键)。

我的问题是为什么Java套接字的行为是这样的,还是我做错了什么?

2 个答案:

答案 0 :(得分:22)

您发现在TCP连接上需要定时器和心跳。

如果拔下网络电缆,TCP连接可能不会断开。如果你没有什么可以发送,TCP / IP堆栈没有什么可以发送,它不知道电缆在某处,或对等PC突然爆炸。在您多年后重新启动服务器之前,可以认为该TCP连接是打开的。

以这种方式思考; TCP连接如何知道另一端从网络中掉线 - 它离开了网络,所以无法告诉你这个事实。

如果您拔下进入服务器的电缆,有些系统可以检测到这种情况,有些则不会。如果你拔掉另一端的电缆,例如以太网交换机,无法检测到。

这就是为什么总是需要主管定时器(例如,向对等方发送心跳消息,或者在给定时间内基于无活动关闭TCP连接)进行TCP连接,

一种非常便宜的方法,至少可以避免只读取数据,从不写入,连续多年保持连接的TCP连接,是在TCP套接字上启用TCP keepalive - 请注意TCP的默认超时keepalive通常是2个小时。

答案 1 :(得分:8)

这些答案都不适用。第一个涉及连接断开的情况,第二个涉及对等方关闭连接的情况。

在TCP连接中,除非发送或接收数据,否则原则上没有任何关于拉断应该断开连接的电缆,因为TCP被故意设计为在这种情况下是健壮的,并且肯定没有关于它应该关注本地应用程序,如同行关闭。

检测TCP中断连接的唯一方法是尝试在其间发送数据,或者在适当的时间间隔后将读取超时解释为丢失连接,这是应用程序决策。

您还可以设置TCP keep-alive on以启用对断开连接的检测,在某些系统中,您甚至可以控制每个套接字的超时。但是不是通过Java,所以你会遇到系统默认设置,除非它被修改,否则应该是两个小时。

您的代码应在调用keyIterator.next()后调用keyIterator.remove()。