我有点不高兴,这不能以优雅的方式处理,在尝试了几个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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
当正在读取SocketChannel时,我拔掉电缆,Selector.select()开始旋转并返回0,现在我没有机会读取或写入通道,因为主要的读写代码是守卫的if (selectedNum > 0),现在这是第一个出现的混乱我的头,从这个答案,据说当通道坏了,select()将返回,并且通道的选择键将指示可读/可写,但显然不是这里的情况,键不是选中,select()仍然返回0.
如果对等关闭套接字:
- 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 socket表现得像这样或者我做错了什么?
nos*_*nos 22
您已经发现在TCP连接上需要计时器和心跳.
如果拔下网络电缆,TCP连接可能不会中断.如果你没有什么可以发送,TCP/IP堆栈没有什么可以发送,它不知道电缆在某处,或对等PC突然爆炸.在您多年后重新启动服务器之前,可以认为该TCP连接是打开的.
这样想吧; TCP连接如何知道另一端从网络中掉线 - 它离开了网络,所以它无法告诉你这个事实.
如果拔下进入服务器的电缆,有些系统可以检测到这种情况,而有些则不会.如果拔下例如以太网交换机另一端的电缆,则无法检测到该电缆.
这就是为什么一个总是需要主管定时器(例如,向对等体发送心跳消息,或者在给定时间内基于无活动关闭TCP连接)进行TCP连接的原因,
一种非常便宜的方法,至少可以避免TCP连接,你只读取数据,从不写入,连续多年保持最新状态,是在TCP套接字上启用TCP keepalive - 请注意TCP keepalive的默认超时通常是2小时.
这些答案都不适用.第一个涉及连接断开的情况,第二个涉及对等方关闭连接的情况.
在TCP连接中,除非发送或接收数据,否则原则上没有任何关于拉断应该断开连接的电缆,因为TCP被故意设计为在这种情况下是健壮的,并且当然没有任何关于它的信息.应该像对等关闭一样查看本地应用程序.
检测TCP中断连接的唯一方法是尝试在其间发送数据,或者在适当的时间间隔后将读取超时解释为丢失的连接,这是应用程序决策.
您还可以设置TCP keep-alive on以启用对断开连接的检测,在某些系统中,您甚至可以控制每个插槽的超时.但是不是通过Java,所以你会遇到系统默认设置,除非经过修改,否则应该是两个小时.
您的代码应在调用keyIterator.next()后调用keyIterator.remove().