【问题标题】:Detecting socket disconnection?检测套接字断开连接?
【发布时间】:2012-12-10 05:13:17
【问题描述】:

在尝试了几个 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();
        }
    }
}

在读取 SocketChannel 时,我拔下电缆,Selector.select() 开始旋转并返回 0,现在我没有机会读取写入频道, 因为主要的读写代码是由if (selectedNum &gt; 0) 守护的,所以现在这是我脑子里冒出来的第一个困惑,从this answer,据说当频道坏了,select() 会返回,而通道的选择键会指示可读/可写,但这里显然不是这样,没有选择键,select() 仍然返回 0。

另外,从EJP's answer 到一个类似的问题:

如果对端关闭套接字:

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

这里也不是这种情况,我尝试注释掉 if (selectedNum &gt; 0) 并使用 selector.keys().iterator() 获取所有键,无论它们是否被选中,从这些键中读取不会返回 -1(而是返回 0),并且写入这些键不会引发EOFException。我只注意到一件事,即使没有选择键,key.isReadable() 返回 true 而 key.isWritable() 返回 false(我想这可能是因为我没有为 OP_WRITE 注册键)。

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

【问题讨论】:

  • 可能是操作系统还没有声明连接断开:您可以重新插入电缆,理论上可以恢复连接。
  • 是的,有时当我重新插入电缆时可以恢复连接,有时select() 只是一直返回 0,我不得不手动取消密钥。

标签: java sockets nio


【解决方案1】:

您发现您需要计时器和 TCP 连接上的心跳。

如果您拔下网线,TCP 连接可能不会中断。如果您没有要发送的内容,TCP/IP 堆栈也没有要发送的内容,它不知道某处电缆已丢失,或者对等 PC 突然起火。在您多年后重新启动服务器之前,可以认为该 TCP 连接是打开的。

这样想; TCP 连接怎么知道另一端掉线了——它已经掉线了,所以它不能告诉你这个事实。

如果您拔下连接服务器的电缆,有些系统可以检测到这一点,有些则不会。如果您拔掉另一端的电缆,例如以太网交换机,不会被检测到。

这就是为什么一个 TCP 连接总是需要主管计时器(例如,向对等方发送心跳消息,或基于在给定时间内没有活动而关闭 TCP 连接)用于 TCP 连接,

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

【讨论】:

  • 您的解释完全消除了我的困惑。但是还有一件事我想知道,有时当我重新插入电缆时,连接恢复了,有时又没有,这是为什么呢?
  • @neevek 可能传输端超时。发送端将检测到另一端已经消失,因为它没有收到任何确认,因此除其他外,它取决于您是否在 tcp 堆栈超时连接之前重新插入电缆。
  • 一个人并不“总是需要主管计时器”。例如,地球上最常用的应用程序协议 HTTP 没有。读取超时和写入时的 IOExceptions 就足够了。
  • 我认为读取超时是一个主管计时器,您通常必须在套接字上显式启用它。如果你不这样做,你可能不会检测到过时的连接(在这种情况下,你可能是 HTTP 服务器/客户端的实现者)..
【解决方案2】:

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

在 TCP 连接中,除非正在发送或接收数据,否则原则上不会拉断连接的电缆,因为 TCP 被刻意设计为在此类事情上保持稳健,当然没有关于它应该像对等关闭一样查看本地应用程序。

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

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

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

【讨论】:

  • 嘿,@EJP,我知道你会来救援,谢谢。 你也可以设置TCP keep-alive开启断连接检测,这里keep-alive在断连接检测中起到什么作用呢?设置和不设置keep-alive有什么区别?至于keyIterator.remove(),我已经在 finally 块中使用了selector.selectedKeys().clear()
  • @neveek 错过了。 TCP keep-alive 不时发送一个数据包,一个需要响应的数据包,如果它没有到达(考虑重试和超时),则连接将被视为断开:您将在下一个 I/O。
  • 我没有实现自定义协议,我使用的是 HTTP,所以如果不时通过网络发送数据包,该数据包会被解释为 HTTP 标头或正文的一部分吗?如果我作为客户端收到 keep-alive 数据包,我该如何处理?
  • 我应该更正我上面的评论。如果 keepalive 使连接断开,您将获得 ECONNTIMEOUT 或其他任何信息,即“连接超时”。请注意与“连接超时”不同的措辞,这是一个连接时间问题。
  • 知道了!但我想知道一年后你怎么还记得这条评论? :)
猜你喜欢
  • 1970-01-01
  • 2016-05-31
  • 1970-01-01
  • 2020-07-04
  • 2013-11-16
  • 1970-01-01
  • 2013-07-22
  • 2019-06-23
  • 2014-04-08
相关资源
最近更新 更多