Java 为什么在不尝试I/O的情况下,不可能检测到TCP套接字被对等方正常关闭?
作为对a的后续,我想知道为什么在Java中,如果不尝试在TCP套接字上读/写,就不可能检测到该套接字已被对等方优雅地关闭?无论是使用NIO之前的Java 为什么在不尝试I/O的情况下,不可能检测到TCP套接字被对等方正常关闭?,java,sockets,tcp,network-programming,Java,Sockets,Tcp,Network Programming,作为对a的后续,我想知道为什么在Java中,如果不尝试在TCP套接字上读/写,就不可能检测到该套接字已被对等方优雅地关闭?无论是使用NIO之前的Socket还是NIOSocketChannel,情况似乎都是如此 当对等方优雅地关闭TCP连接时,连接两侧的TCP堆栈都知道这一事实。服务器端(启动关机的那一方)最终处于状态FIN_WAIT2,而客户端(没有明确响应关机的那一方)最终处于状态CLOSE\u WAIT。为什么在Socket或SocketChannel中没有可以查询TCP堆栈以查看底层TC
Socket
还是NIOSocketChannel
,情况似乎都是如此
当对等方优雅地关闭TCP连接时,连接两侧的TCP堆栈都知道这一事实。服务器端(启动关机的那一方)最终处于状态FIN_WAIT2
,而客户端(没有明确响应关机的那一方)最终处于状态CLOSE\u WAIT
。为什么在Socket
或SocketChannel
中没有可以查询TCP堆栈以查看底层TCP连接是否已终止的方法?TCP堆栈是否不提供此类状态信息?还是避免对内核进行代价高昂的调用是一种设计决策
在已经发布了这个问题的一些答案的用户的帮助下,我想我看到了问题的来源。未明确关闭连接的一方最终处于TCP状态close\u WAIT
,这意味着连接正在关闭过程中,并等待该方发出自己的close
操作。我认为,isConnected
返回true
和isClosed
返回false
是很公平的,但是为什么没有类似isClosed
的东西呢
下面是使用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);
}
}
底层套接字API没有这样的通知
发送TCP堆栈直到最后一个数据包才会发送FIN位,因此在发送数据之前,当发送应用程序逻辑上关闭其套接字时,可能会有大量数据被缓冲。同样,由于网络比接收应用程序快,所以缓冲的数据(我不知道,可能是通过较慢的连接进行中继)可能对接收方很重要,并且您不希望接收应用程序仅仅因为堆栈已接收到FIN位就放弃它。底层套接字API没有此类通知
发送TCP堆栈直到最后一个数据包才会发送FIN位,因此在发送数据之前,当发送应用程序逻辑上关闭其套接字时,可能会有大量数据被缓冲。同样,由于网络比接收应用程序快(我不知道,可能是通过较慢的连接进行中继)而被缓冲的数据可能对接收器很重要,并且您不会希望接收应用程序仅仅因为堆栈接收到FIN位就丢弃它。这是一个有趣的话题。我刚才已经翻遍了java代码以进行检查。根据我的发现,有两个截然不同的问题:第一个是TCP RFC本身,它允许远程关闭的套接字以半双工方式传输数据,因此远程关闭的套接字仍然是半开的。根据RFC,RST不关闭连接,您需要发送一个显式中止命令;所以Java允许通过半封闭的套接字发送数据 (有两种方法可以读取两个端点处的关闭状态。)
另一个问题是,实现说这种行为是可选的。当Java努力实现可移植性时,它们实现了最好的通用特性。我想维护(操作系统,半双工的实现)的映射可能是个问题。这是一个有趣的话题。我刚才已经翻遍了java代码以进行检查。根据我的发现,有两个截然不同的问题:第一个是TCP RFC本身,它允许远程关闭的套接字以半双工方式传输数据,因此远程关闭的套接字仍然是半开的。根据RFC,RST不关闭连接,您需要发送一个显式中止命令;所以Java允许通过半封闭的套接字发送数据 (有两种方法可以读取两个端点处的关闭状态。)
另一个问题是,实现说这种行为是可选的。当Java努力实现可移植性时,它们实现了最好的通用特性。我想维护(操作系统,半双工的实现)的映射可能是个问题。我认为这更像是一个套接字编程问题。Java只是遵循套接字编程的传统 发件人: TCP提供可靠的、有序的 从一个服务器传送字节流 将一台计算机上的程序连接到另一台计算机上 另一台计算机上的程序 握手完成后,TCP不会对两个端点(客户端和服务器)进行任何区分。术语“客户机”和“服务器”主要是为了方便。因此,“服务器”可以发送数据,“客户端”可以同时向彼此发送一些其他数据 “关闭”一词也具有误导性。只有FIN声明,意思是“我不会再给你寄东西了。”但这并不意味着航班上没有包裹,或者对方没有更多的话要说。如果您将snail mail实现为数据链路层,或者如果您的数据包经过不同的路径,那么接收方可能会以错误的顺序接收数据包。TCP知道如何为您解决此问题
此外,作为一个程序,您可能没有时间继续检查缓冲区中的内容。因此,在您方便的时候,您可以检查缓冲区中的内容。总而言之,当前的套接字实现并没有那么糟糕。如果确实存在isPeerClosed(),那么每次调用read时都必须进行额外的调用。我认为这更像是一个套接字编程问题。Java只是遵循套接字编程的传统 发件人: TCP提供可靠的、有序的 b流的交付
connected: true, closed: false
connected: true, closed: false
...
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 */
}
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
}