Java 为什么在不尝试 I/O 的情况下不可能检测到 TCP 套接字已被对等端正常关闭?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/155243/
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-08-11 09:02:34  来源:igfitidea点击:

Why is it impossible, without attempting I/O, to detect that TCP socket was gracefully closed by peer?

javasocketstcpnetwork-programming

提问by Alexander

As a follow up to a recent question, I wonder why it is impossible in Java, without attempting reading/writing on a TCP socket, to detect that the socket has been gracefully closed by the peer? This seems to be the case regardless of whether one uses the pre-NIO Socketor the NIO SocketChannel.

作为对最近一个问题的跟进,我想知道为什么在 Java 中,如果不尝试在 TCP 套接字上读/写,就不可能检测到套接字已被对等端正常关闭?无论是使用 pre-NIOSocket还是 NIO ,情况似乎都是如此SocketChannel

When a peer gracefully closes a TCP connection, the TCP stacks on both sides of the connection know about the fact. The server-side (the one that initiates the shutdown) ends up in state FIN_WAIT2, whereas the client-side (the one that does not explicitly respond to the shutdown) ends up in state CLOSE_WAIT. Why isn't there a method in Socketor SocketChannelthat can query the TCP stack to see whether the underlying TCP connection has been terminated? Is it that the TCP stack doesn't provide such status information? Or is it a design decision to avoid a costly call into the kernel?

当对等方正常关闭 TCP 连接时,连接双方的 TCP 堆栈都知道这一事实。服务器端(启动关闭的那个)以 state 结束FIN_WAIT2,而客户端(没有明确响应关闭的那个)以 state 结束CLOSE_WAIT。为什么在Socketor 中SocketChannel没有可以查询 TCP 堆栈以查看底层 TCP 连接是否已终止的方法?是不是 TCP 堆栈没有提供这样的状态信息?或者是为了避免对内核进行代价高昂的调用而做出的设计决定?

With the help of the users who have already posted some answers to this question, I think I see where the issue might be coming from. The side that doesn't explicitly close the connection ends up in TCP state CLOSE_WAITmeaning that the connection is in the process of shutting down and waits for the side to issue its own CLOSEoperation. I suppose it's fair enough that isConnectedreturns trueand isClosedreturns false, but why isn't there something like isClosing?

在已经发布了这个问题的一些答案的用户的帮助下,我想我知道问题可能来自哪里。未明确关闭连接的一侧最终处于 TCP 状态,CLOSE_WAIT这意味着连接正在关闭并等待一侧发出自己的CLOSE操作。我认为isConnected返回trueisClosed返回足够公平false,但为什么没有类似的东西isClosing

Below are the test classes that use pre-NIO sockets. But identical results are obtained using NIO.

下面是使用 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);
  }
}

When the test client connects to the test server the output remains unchanged even after the server initiates the shutdown of the connection:

当测试客户端连接到测试服务器时,即使在服务器启动连接关闭后,输出也保持不变:

connected: true, closed: false
connected: true, closed: false
...

回答by Mike Dimmick

The underlying sockets API doesn't have such a notification.

底层套接字 API 没有这样的通知。

The sending TCP stack won't send the FIN bit until the last packet anyway, so there could be a lot of data buffered from when the sending application logically closed its socket before that data is even sent. Likewise, data that's buffered because the network is quicker than the receiving application (I don't know, maybe you're relaying it over a slower connection) could be significant to the receiver and you wouldn't want the receiving application to discard it just because the FIN bit has been received by the stack.

无论如何,发送 TCP 堆栈在最后一个数据包之前不会发送 FIN 位,因此当发送应用程序在该数据发送之前逻辑关闭其套接字时,可能会缓冲大量数据。同样,由于网络比接收应用程序更快而缓冲的数据(我不知道,也许您正在通过较慢的连接中继它)可能对接收器很重要,您不希望接收应用程序丢弃它只是因为堆栈已接收到 FIN 位。

回答by Lorenzo Boccaccia

It's an interesting topic. I've dug through the java code just now to check. From my finding, there are two distinct problems: the first is the TCP RFC itself, which allows for remotely closed socket to transmit data in half-duplex, so a remotely closed socket is still half open. As per the RFC, RST doesn't close the connection, you need to send an explicit ABORT command; so Java allow for sending data through half closed socket

这是一个有趣的话题。我刚刚翻阅了java代码来检查一下。根据我的发现,有两个不同的问题:第一个是 TCP RFC 本身,它允许远程关闭的套接字以半双工方式传输数据,因此远程关闭的套接字仍然是半打开的。根据 RFC,RST 不会关闭连接,您需要发送显式 ABORT 命令;所以Java允许通过半封闭的套接字发送数据

(There are two methods for reading the close status at both of the endpoint.)

(有两种方法可以读取两端的关闭状态。)

The other problem is that the implementation say that this behavior is optional. As Java strives to be portable, they implemented the best common feature. Maintaining a map of (OS, implementation of half duplex) would have been a problem, I guess.

另一个问题是实现说这种行为是可选的。由于 Java 力求可移植,他们实现了最好的通用功能。我想,维护(操作系统,半双工的实现)的地图会是一个问题。

回答by Eugene Yokota

I think this is more of a socket programming question. Java is just following the socket programming tradition.

我认为这更像是一个套接字编程问题。Java 只是遵循套接字编程传统。

From Wikipedia:

来自维基百科

TCP provides reliable, ordered delivery of a stream of bytes from one program on one computer to another program on another computer.

TCP 提供从一台计算机上的一个程序到另一台计算机上的另一个程序的可靠、有序的字节流传输。

Once the handshake is done, TCP does not make any distinction between two end points (client and server). The term "client" and "server" is mostly for convenience. So, the "server" could be sending data and "client" could be sending some other data simultaneously to each other.

握手完成后,TCP 不会在两个端点(客户端和服务器)之间进行任何区分。术语“客户端”和“服务器”主要是为了方便。因此,“服务器”可能正在发送数据,而“客户端”可能会同时向彼此发送一些其他数据。

The term "Close" is also misleading. There's only FIN declaration, which means "I am not going to send you any more stuff." But this does not mean that there are no packets in flight, or the other has no more to say. If you implement snail mail as the data link layer, or if your packet traveled different routes, it's possible that the receiver receives packets in wrong order. TCP knows how to fix this for you.

“关闭”一词也具有误导性。只有FIN声明,意思是“我不会再给你寄东西了”。但这并不代表没有数据包在飞行,或者对方无话可说。如果您将蜗牛邮件用作数据链路层,或者您的数据包经过不同的路由,则接收方可能会以错误的顺序接收数据包。TCP 知道如何为您解决这个问题。

Also you, as a program, may not have time to keep checking what's in the buffer. So, at your convenience you can check what's in the buffer. All in all, current socket implementation is not so bad. If there actually were isPeerClosed(), that's extra call you have to make every time you want to call read.

此外,作为程序,您可能没有时间继续检查缓冲区中的内容。因此,您可以在方便时检查缓冲区中的内容。总而言之,当前的套接字实现还不错。如果确实有 isPeerClosed(),那么每次要调用 read 时都必须进行额外调用。

回答by WMR

The reason for this behaviour (which is not Java specific) is the fact that you don't get any status information from the TCP stack. After all, a socket is just another file handle and you can't find out if there's actual data to read from it without actually trying to (select(2)won't help there, it only signals that you can try without blocking).

这种行为(不是 Java 特定的)的原因是您没有从 TCP 堆栈中获得任何状态信息。毕竟,套接字只是另一个文件句柄,如果不实际尝试,您就无法确定是否有实际数据要从中读取(select(2)在那里无济于事,它只会表明您可以在不阻塞的情况下尝试)。

For more information see the Unix socket FAQ.

有关更多信息,请参阅Unix 套接字常见问题解答

回答by Alexander

Since none of the answers so far fully answer the question, I'm summarizing my current understanding of the issue.

由于到目前为止没有一个答案完全回答这个问题,我总结了我目前对这个问题的理解。

When a TCP connection is established and one peer calls close()or shutdownOutput()on its socket, the socket on the other side of the connection transitions into CLOSE_WAITstate. In principle, it's possible to find out from the TCP stack whether a socket is in CLOSE_WAITstate without calling read/recv(e.g., getsockopt()on Linux: http://www.developerweb.net/forum/showthread.php?t=4395), but that's not portable.

当 TCP 连接建立并且一个对等方调用close()shutdownOutput()其套接字时,连接另一侧的套接字转换为CLOSE_WAIT状态。原则上,可以在CLOSE_WAIT不调用的情况下从 TCP 堆栈中找出套接字是否处于状态read/recv(例如,getsockopt()在 Linux 上:http: //www.developerweb.net/forum/showthread.php?t= 4395),但事实并非如此便携的。

Java's Socketclass seems to be designed to provide an abstraction comparable to a BSD TCP socket, probably because this is the level of abstraction to which people are used to when programming TCP/IP applications. BSD sockets are a generalization supporting sockets other than just INET (e.g., TCP) ones, so they don't provide a portable way of finding out the TCP state of a socket.

Java 的Socket类似乎旨在提供类似于 BSD TCP 套接字的抽象,可能是因为这是人们在编写 TCP/IP 应用程序时习惯使用的抽象级别。BSD 套接字是支持套接字的泛化,而不仅仅是 INET(例如 TCP)套接字,因此它们不提供查找套接字 TCP 状态的可移植方式。

There's no method like isCloseWait()because people used to programming TCP applications at the level of abstraction offered by BSD sockets don't expect Java to provide any extra methods.

没有类似的方法,isCloseWait()因为人们习惯于在 BSD 套接字提供的抽象级别上编写 TCP 应用程序,不希望 Java 提供任何额外的方法。

回答by Ray

Only writes require that packets be exchanged which allows for the loss of connection to be determined. A common work around is to use the KEEP ALIVE option.

只有写入需要交换数据包,这允许确定连接丢失。一种常见的解决方法是使用 KEEP ALIVE 选项。

回答by Joshua

This is a flaw of Java's (and all others' that I've looked at) OO socket classes -- no access to the select system call.

这是 Java 的(以及我看过的所有其他人的)OO 套接字类的一个缺陷——无法访问 select 系统调用。

Correct answer in C:

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 */  
}  

回答by Joshua

the Java IO stack definitely sends FIN when it gets destructed on an abrupt teardown. It just makes no sense that you can't detect this, b/c most clients only send the FIN if they are shutting down the connection.

Java IO 堆栈在突然拆卸时被破坏时肯定会发送 FIN。您无法检测到这一点是没有意义的,b/c 大多数客户端仅在关闭连接时才发送 FIN。

...another reason i am really beginning to hate the NIO Java classes. It seems like everything is a little half-ass.

...我真的开始讨厌 NIO Java 类的另一个原因。似乎一切都有点半屁股。

回答by Uncle Per

Detecting whether the remote side of a (TCP) socket connection has closed can be done with the java.net.Socket.sendUrgentData(int) method, and catching the IOException it throws if the remote side is down. This has been tested between Java-Java, and Java-C.

可以使用 java.net.Socket.sendUrgentData(int) 方法检测(TCP)套接字连接的远程端是否已关闭,并在远程端关闭时捕获它抛出的 IOException。这已经在 J​​ava-Java 和 Java-C 之间进行了测试。

This avoids the problem of designing the communication protocol to use some sort of pinging mechanism. By disabling OOBInline on a socket (setOOBInline(false), any OOB data received is silently discarded, but OOB data can still be sent. If the remote side is closed, a connection reset is attempted, fails, and causes some IOException to be thrown.

这避免了设计通信协议以使用某种 ping 机制的问题。通过在套接字上禁用 OOBInline (setOOBInline(false),任何接收到的 OOB 数据都将被静默丢弃,但仍然可以发送 OOB 数据。如果远程端关闭,则尝试重置连接,失败,并导致抛出一些 IOException .

If you actually use OOB data in your protocol, then your mileage may vary.

如果您在协议中实际使用 OOB 数据,那么您的里程可能会有所不同。

回答by JimmyB

When it comes to dealing with half-open Java sockets, one might want to have a look at isInputShutdown()and isOutputShutdown().

当涉及到处理半开 Java 套接字时,您可能想看看 isInputShutdown()isOutputShutdown()