java 检测套接字断开连接?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14010194/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 14:43:07  来源:igfitidea点击:

Detecting socket disconnection?

javasocketsnio

提问by neevek

I am kinda upset that this cannot be handled in an elegant way, after trying different solutions (this, thisand several others) mentioned in answers to several SO questions, I still could not manage to detect socket disconnection (by unplugging cable).

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

I am using NIO non-blocking socket, everything works perfectly except that I find no way of detecting server disconnection.

我正在使用 NIO 非阻塞套接字,除了我找不到检测服务器断开连接的方法之外,一切都完美无缺。

I have the following code:

我有以下代码:

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();
        }
    }
}

While the SocketChannels are being read, I unplug the cable, and Selector.select()starts spinning and returning 0, now I have no chance to reador writethe channels, because the main reading & writing code is guarded by if (selectedNum > 0), now this is the first confusion coming out of my head, from this answer, it is said that when the channel is broken, select() will return, and the selection key for the channel will indicate readable/writable, but it is apparently not the case here, the keys are not selected, select()still returns 0.

在读取 SocketChannels 时,我拔掉电缆,Selector.select()开始旋转并返回 0,现在我没有机会读取写入通道,因为主要的读写代码由 保护if (selectedNum > 0),现在这是第一个出现的混乱在我的脑海中,从这个答案中,据说当通道损坏时,select() 将返回,并且通道的选择键将指示 可读/可写,但这里显然不是这种情况,键不是选择,select()仍然返回 0。

Also, from EJP's answerto a similar question:

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

If the peer closes the socket:

  • read() returns -1
  • readLine() returns null
  • readXXX() throws EOFException, for any other X.

如果对等方关闭套接字:

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

Not the case here either, I tried commenting out if (selectedNum > 0)and using selector.keys().iterator()to get all the keys regardless whether or not they are selected, reading from those keys does not return -1 (0 returned instead), and writing to those keys does not get EOFExceptionthrown. I only noted one thing, that even the keys are not selected, key.isReadable()returns true while key.isWritable()returns false (I guess this might be because I didn't register the keys for OP_WRITE).

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

My question is why Java socket is behaving like this or is there something I did wrong?

我的问题是为什么 Java 套接字会出现这样的行为,或者我做错了什么?

回答by nos

You have discovered that you need timers, and heartbeat on a TCP connection.

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

If you unplug the network cable, the TCP connection may not be broken. If you have nothing to send, the TCP/IP stack have nothing to send, it doesn't know that a cable is gone somewhere, or that the peer PC suddenly burst into flames. That TCP connection could be considered open until you reboot your server years later.

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

Think of it this way; how can the TCP connection know that the other end dropped off the network - it's off the network so it can't tell you that fact.

这样想;TCP 连接如何知道另一端已断开网络 - 它已断开网络,因此它无法告诉您这个事实。

Some systems can detect this if you unplug the cable going into your server, and some will not. If you unplug the cable at the other end of e.g. an ethernet switch, that will not be detected.

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

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,

这就是为什么对于 TCP 连接总是需要监督计时器(例如,向对等方发送心跳消息,或基于在给定时间内没有活动的情况下关闭 TCP 连接)的原因,

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.

一种至少避免 TCP 连接的非常便宜的方法是在 TCP 套接字上启用 TCP keepalive,因为 TCP keepalive 的默认超时通常为2小时。

回答by user207421

Neither those answers applies. The first one concerns the case when the connection is broken, and the second one (mine) concerns the case where the peer closes the connection.

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

In a TCP connection, unless data is being sent or received, there is in principle nothing about pulling a cable that should break a connection, as TCP is deliberately designed to be robust across this sort of thing, and there is certainly nothing about it that should look to the local application like the peer closing.

在 TCP 连接中,除非正在发送或接收数据,否则原则上不会拉断连接的电缆,因为 TCP 被故意设计为在这种情况下是健壮的,而且肯定没有任何关于它的内容应该像对等关闭一样查看本地应用程序。

The only way to detect a broken connection in TCP is to attempt to send data across it, or to interpret a read timeout as a lost connection after a suitable interval, which is an application decision.

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

You can also set TCP keep-alive on to enable detection of broken connections, and in some systems you can even control the timeout per socket. Not via Java however, so you are stuck with the system default, which should be two hours unless it has been modified.

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

Your code should call keyIterator.remove() after calling keyIterator.next().

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