16

在尝试了几个 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,现在我没有机会读取写入通道,因为主要的读写代码由 保护if (selectedNum > 0),现在这是第一个出现的混乱在我的脑海中,从这个答案中,据说当频道被破坏时,select()会返回,频道的选择键将指示可读/可写,但这里显然不是这种情况,键不是选择,select()仍然返回 0。

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

如果对等方关闭套接字:

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

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

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

4

2 回答 2

22

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

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

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

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

That's why one always need supervisor timers(that e.g. send a heartbeat message to the peer, or close a TCP connection based on no activity for a given amount of time) for a TCP connection,

One very cheap way to at least avoid TCP connections that you only read data from, never write to, to stay up for years on end, is to enable TCP keepalive on a TCP socket - be aware that the default timeouts for TCP keepalive is often 2 hours.

于 2012-12-23T21:43:21.960 回答
8

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

在 TCP 连接中,除非正在发送或接收数据,否则原则上没有任何关于拉断连接的电缆,因为 TCP 被刻意设计为在这类事情上具有鲁棒性,而且肯定没有任何关于它的应该像对等关闭一样查看本地应用程序。

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

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

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

于 2012-12-23T20:43:58.290 回答