java - 为什么在没有尝试I / O的情况下检测TCP套接字是否被对等端正常关闭是不可能的?
作为最近一个问题的后续内容,我想知道为什么在没有尝试在TCP套接字上进行读/写操作的情况下,为什么Java不可能检测到套接字已被对等端正常关闭? 无论是否使用前NIO CLOSE_WAIT或NIO CLOSE,情况似乎都是如此。
当对等体正常关闭TCP连接时,连接两端的TCP堆栈都知道这一事实。 服务器端(启动关闭的那个)最终处于状态CLOSE_WAIT,而客户端(未明确响应关闭的那个)最终处于状态CLOSE.为什么没有方法 在isConnected或true中,可以查询TCP堆栈以查看底层TCP连接是否已终止? 是不是TCP堆栈没有提供这样的状态信息? 或者这是一个设计决定,以避免昂贵的内核调用?
在已经发布了这个问题的答案的用户的帮助下,我想我会看到问题可能来自哪里。 没有明确关闭连接的一方最终处于TCP状态CLOSE_WAIT,这意味着连接正在关闭并等待一方发出自己的CLOSE操作。 我认为这是公平的,isConnected返回true和isClosed返回false,但为什么没有像isClosing这样的东西?
以下是使用pre-NIO套接字的测试类。 但使用NIO可获得相同的结果。
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) throws Exception {
final ServerSocket ss = new ServerSocket(12345);
final Socket cs = ss.accept();
System.out.println("Accepted connection");
Thread.sleep(5000);
cs.close();
System.out.println("Closed connection");
ss.close();
Thread.sleep(100000);
}
}
import java.net.Socket;
public class MyClient {
public static void main(String[] args) throws Exception {
final Socket s = new Socket("localhost", 12345);
for (int i = 0; i < 10; i++) {
System.out.println("connected: " + s.isConnected() +
", closed: " + s.isClosed());
Thread.sleep(1000);
}
Thread.sleep(100000);
}
}
当测试客户端连接到测试服务器时,即使服务器启动连接关闭,输出仍保持不变:
connected: true, closed: false
connected: true, closed: false
...
12个解决方案
29 votes
我一直在使用套接字,主要是使用选择器,虽然不是网络OSI专家,但根据我的理解,在Socket上调用Socket.getInputStream().read() < 0实际上会在网络上发送一些东西(FIN)唤醒另一边的Selector(相同的行为) C语言)。 在这里你有检测:实际检测到你尝试时会失败的读操作。
在您提供的代码中,关闭套接字将关闭输入和输出流,而无法读取可能的数据,从而丢失它们。 Java Socket.getInputStream().read() < 0方法执行一个优雅的&#34; 断开(与我最初的想法相反),因为输出流中剩余的数据将被发送,然后是FIN以发信号通知其关闭。 FIN将由另一方确认,就像任何常规数据包一样。
如果你需要等待另一方关闭它的套接字,你需要等待它的FIN。 要实现这一点,你必须检测Socket.getInputStream().read() < 0,这意味着你不应该关闭你的套接字,因为它会关闭它的while。
从我在C中所做的,现在在Java中,实现这样的同步关闭应该像这样:
关闭套接字输出(在另一端发送FIN,这是此套接字将发送的最后一件事)。 输入仍然是打开所以你可以while并检测远程InputStream
读取套接字while,直到我们从另一端收到reply-FIN(因为它将检测FIN,它将经历相同的优雅的diconnection过程)。 这在某些操作系统上非常重要,因为只要其中一个缓冲区仍包含数据,它们就不会实际关闭套接字。 他们被称为&#34;鬼&#34; 套接字并在操作系统中使用描述符编号(现代操作系统可能不再是问题)
关闭套接字(通过调用while或关闭其InputStream或OutputStream)
如以下Java代码段所示:
public void synchronizedClose(Socket sok) {
InputStream is = sok.getInputStream();
sok.shutdownOutput(); // Sends the 'FIN' on the network
while (is.read() > 0) ; // "read()" returns '-1' when the 'FIN' is reached
sok.close(); // or is.close(); Now we can close the Socket
}
当然,双方必须使用相同的关闭方式,或者发送部分可能始终发送足够的数据以保持while循环忙(例如,如果发送部分仅发送数据而从不读取以检测连接终止。这是笨拙的 ,但你可能无法控制那个)。
正如@WarrenDew在他的评论中指出的那样,丢弃程序(应用程序层)中的数据会导致应用程序层的非正常断开:尽管所有数据都是在TCP层(while循环)接收的,但它们将被丢弃。
1:来自&#34; Java中的基础网络&#34;:见图。 3.3 p.45,以及整个§3.7,第43-48页
Matthieu answered 2019-08-22T04:22:00Z
18 votes
我认为这更像是一个套接字编程问题。 Java只是遵循套接字编程传统。
来自维基百科:
TCP提供可靠,有序 从一个字节传递字节流 将计算机上的程序转移到另一台 程序在另一台计算机上
握手完成后,TCP不会对两个端点(客户端和服务器)进行任何区分。 术语&#34;客户&#34; 和&#34;服务器&#34; 主要是为了方便。 所以,&#34;服务器&#34; 可以发送数据和&#34;客户端&#34; 可能是彼此同时发送一些其他数据。
术语&#34;关闭&#34; 也有误导性。 这是唯一的FIN声明,这意味着&#34;我不会再向你发送任何东西了。&#34; 但这并不意味着没有飞行中的数据包,或者另一个没有更多的话要说。 如果您将蜗牛邮件作为数据链路层实施,或者您的数据包通过不同的路由,则接收器可能会以错误的顺序接收数据包。 TCP知道如何为您解决此问题。
此外,作为一个程序,您可能没有时间继续检查缓冲区中的内容。 因此,在您方便时,您可以检查缓冲区中的内容。 总而言之,当前的套接字实现并不是那么糟糕。 如果确实存在isPeerClosed(),则每次要调用read时都必须进行额外调用。
Eugene Yokota answered 2019-08-22T04:22:59Z
11 votes
底层套接字API没有这样的通知。
无论如何,发送TCP堆栈都不会发送FIN位直到最后一个数据包,因此在发送应用程序在发送数据之前逻辑上关闭其套接字时可能会缓冲大量数据。 同样,由于网络比接收应用程序更快而缓冲的数据(我不知道,也许你通过较慢的连接中继它)可能对接收器很重要而且你不会# 39;不希望接收应用程序丢弃它只是因为堆栈已经接收到FIN位。
Mike Dimmick answered 2019-08-22T04:23:35Z
8 votes
由于到目前为止没有一个答案完全回答了这个问题,我总结了我目前对这个问题的理解。
建立TCP连接并且一个对等体在其套接字上调用isCloseWait()或shutdownOutput()时,连接另一端的套接字将转换为CLOSE_WAIT状态。 原则上,可以从TCP堆栈中找出套接字是否处于CLOSE_WAIT状态而不调用read/recv(例如,Linux上的getsockopt():[http://www.developerweb.net/forum/showthread.php? t = 4395),]但那不便携。
Java的isCloseWait()类似乎旨在提供与BSD TCP套接字相当的抽象,可能是因为这是人们在编写TCP / IP应用程序时习惯的抽象级别。 BSD套接字是除了INET(例如TCP)之外的支持套接字的泛化,因此它们不提供查找套接字的TCP状态的可移植方式。
没有像isCloseWait()这样的方法,因为人们习惯在BSD套接字提供的抽象级别编写TCP应用程序并不期望Java提供任何额外的方法。
Alexander answered 2019-08-22T04:24:26Z
7 votes
检测(TCP)套接字连接的远程端是否已关闭可以使用java.net.Socket.sendUrgentData(int)方法完成,如果远程端关闭,则捕获它抛出的IOException。 这已经在Java-Java和Java-C之间进行了测试。
这避免了设计通信协议以使用某种ping机制的问题。 通过在套接字上禁用OOBInline(setOOBInline(false),接收的任何OOB数据都将被静默丢弃,但仍可以发送OOB数据。如果远程端关闭,则尝试连接重置,失败并导致抛出某些IOException。
如果您在协议中实际使用OOB数据,那么您的里程可能会有所不同。
Uncle Per answered 2019-08-22T04:25:10Z
4 votes
当Java IO堆栈在突然拆除时被破坏时,它肯定会发送FIN。 你无法检测到这一点是没有意义的,大多数客户只有在关闭连接时才发送FIN。
...我真的开始讨厌NIO Java类的另一个原因。 似乎一切都有点半屁股。
answered 2019-08-22T04:25:46Z
4 votes
这是一个有趣的话题。 我刚刚通过java代码挖掘来检查。 根据我的发现,有两个不同的问题:第一个是TCP RFC本身,它允许远程关闭套接字以半双工方式传输数据,因此远程关闭的套接字仍然是半开放的。 根据RFC,RST不会关闭连接,您需要发送一个明确的ABORT命令; 所以Java允许通过半封闭套接字发送数据
(有两种方法可以在两个端点读取关闭状态。)
另一个问题是实现说这种行为是可选的。 随着Java努力实现可移植性,他们实现了最佳的通用功能。 我想,维护(OS,实现半双工)的地图本来就是个问题。
Lorenzo Boccaccia answered 2019-08-22T04:26:31Z
3 votes
这是Java的缺陷(以及我所看到的所有其他人)OO套接字类 - 无法访问select系统调用。
C中的正确答案:
struct timeval tp;
fd_set in;
fd_set out;
fd_set err;
FD_ZERO (in);
FD_ZERO (out);
FD_ZERO (err);
FD_SET(socket_handle, err);
tp.tv_sec = 0; /* or however long you want to wait */
tp.tv_usec = 0;
select(socket_handle + 1, in, out, err, &tp);
if (FD_ISSET(socket_handle, err) {
/* handle closed socket */
}
Joshua answered 2019-08-22T04:27:06Z
2 votes
这是一个蹩脚的解决方法。 使用SSL;)和SSL在拆解时进行紧密握手,因此您会收到关闭套接字的通知(大多数实现似乎都在进行属性握手拆除)。
Dean Hiller answered 2019-08-22T04:27:35Z
2 votes
这种行为(不是Java特定的)的原因是您没有从TCP堆栈获取任何状态信息。 毕竟,套接字只是另一个文件句柄,你无法确定是否有实际数据从中读取而没有实际尝试(select(2)在那里没有帮助,它只发出信号表示你 可以不受阻碍地尝试)。
有关更多信息,请参阅Unix套接字FAQ。
WMR answered 2019-08-22T04:28:11Z
0 votes
只有写入才需要交换数据包,这样才能确定连接丢失。 常见的解决方法是使用KEEP ALIVE选项。
Ray answered 2019-08-22T04:28:40Z
-3 votes
在处理半开放的Java套接字时,人们可能想看看isInputShutdown()和isOutputShutdown()。
JimmyB answered 2019-08-22T04:29:09Z