我有点不高兴的是,在尝试了几个SO问题的答案中提到的不同解决方案(this,this和其他几个)之后,这无法以优雅的方式处理,我仍然无法管理检测插座断开(通过拔下电缆)。
我正在使用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套接字的行为是这样的,还是我做错了什么?
答案 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()。