检测插座断开?

Detecting socket disconnection?

提问人:neevek 提问时间:12/23/2012 最后编辑:Communityneevek 更新时间:5/9/2017 访问量:21657

问:

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

我正在使用 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 时,我拔下电缆,开始旋转并返回 0,现在我没有机会读取写入通道,因为主要的读写代码由 ,现在这是我脑海中出现的第一个困惑,从这个答案来看,据说当通道断开时, select() 将返回,通道的选择键将指示可读/可写,但这里显然不是这样,键没有被选中,仍然返回 0。Selector.select()if (selectedNum > 0)select()

此外,从 EJP 对类似问题的回答:

如果对等方关闭套接字:

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

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

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

Java 套接字 NIO

评论

1赞 MvG 12/23/2012
可能是操作系统尚未声明连接中断:您可以重新插入电缆,理论上可以恢复连接。
0赞 neevek 12/23/2012
是的,有时当我重新插入电缆时可以恢复连接,有时只是不断返回 0,我不得不手动取消密钥。select()

答:

9赞 user207421 12/24/2012 #1

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

在TCP连接中,除非正在发送或接收数据,否则原则上不会拉动电缆来中断连接,因为TCP被故意设计为在这种事情上是健壮的,当然也没有什么可以像对等关闭那样在本地应用程序上看到的。

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

您还可以将 TCP 保持活动状态设置为打开以启用对断开连接的检测,在某些系统中,您甚至可以控制每个套接字的超时。但是,不是通过 Java,因此您只能使用系统默认值,除非对其进行修改,否则应该是两个小时。

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

评论

0赞 neevek 12/24/2012
嘿,@EJP,我就知道你会来救援的,谢谢。您还可以将 TCP keep-alive 设置为启用对断开连接的检测,keep-alive 在断开连接检测中扮演什么角色?设置和不设置 keep-alive 有什么区别?至于,我已经在finally块中使用过了。keyIterator.remove()selector.selectedKeys().clear()
0赞 user207421 12/24/2012
@neveek错过了。TCP keep-alive 时不时地发送一个数据包,一个需要响应的数据包,如果它没有到达(考虑到重试和超时),连接将被视为中断:您将在下一次 I/O 上获得“连接重置”。
0赞 neevek 12/24/2012
我没有实现自定义协议,而是使用 HTTP,因此,如果时不时地通过网络发送数据包,该数据包是否会被解释为 HTTP 标头或正文的一部分?如果我作为客户端收到 keep-alive 数据包,我该如何处理?
1赞 user207421 2/24/2014
我应该纠正我上面的评论。如果 keepalive 使连接断开,您将获得 ECONNTIMEOUT 或其他任何内容,“连接超时”。请注意与“连接超时”不同的措辞,这是一个连接时间问题。
1赞 neevek 2/24/2014
明白了!但我想知道 1 年后你怎么还记得这条评论?:)
26赞 nos 12/24/2012 #2

您发现 TCP 连接上需要计时器和检测信号。

如果拔下网线,TCP 连接可能不会断开。如果没有什么可发送的,TCP/IP 堆栈就没有什么可发送的,它不知道电缆在某处消失了,或者对等 PC 突然起火了。该 TCP 连接可以被视为打开,直到您几年后重新启动服务器。

这样想吧;TCP连接怎么知道另一端脱离了网络 - 它已经脱离了网络,所以它不能告诉你这个事实。

如果您拔下进入服务器的电缆,某些系统可以检测到这一点,而有些系统则不会。如果您在以太网交换机的另一端拔下电缆,则不会检测到。

这就是为什么对于TCP连接,总是需要supervisor计时器(例如,向对等方发送心跳消息,或根据给定时间内没有活动关闭TCP连接)。

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

评论

0赞 neevek 12/24/2012
你的解释完全消除了我的困惑。但是我仍然想知道一件事,有时当我重新插入电缆时,连接会恢复,有时则不会,这是为什么?
1赞 nos 12/24/2012
@neevek 传输结束可能超时。传输端将检测到另一端不见了,因为它没有收到任何确认,因此除其他提示外,这将取决于您是否在 tcp 堆栈超时连接之前重新插入电缆。
0赞 user207421 5/18/2017
人们并不“总是需要主管计时器”。例如,HTTP是地球上最常用的应用程序原型,但没有。写入时的读取超时和 IOExceptions 就足够了。
0赞 nos 5/18/2017
我认为读取超时是主管计时器,通常必须在套接字上显式启用。如果你不这样做,你就有可能检测不到过时的连接(在这种情况下,你可能是HTTP服务器/客户端的实现者)。