为什么在不尝试 I/O 的情况下不可能检测到 TCP 套接字被对等端正常关闭? [英] Why is it impossible, without attempting I/O, to detect that TCP socket was gracefully closed by peer?

查看:19
本文介绍了为什么在不尝试 I/O 的情况下不可能检测到 TCP 套接字被对等端正常关闭?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 Socket or the NIO SocketChannel.

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

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 Socket or SocketChannel that 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 状态 CLOSE_WAIT 意味着连接正在关闭并等待一方发出自己的 CLOSE> 操作.我认为 isConnected 返回 trueisClosed 返回 false 是很公平的,但为什么没有类似的东西isClosing?

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_WAIT meaning that the connection is in the process of shutting down and waits for the side to issue its own CLOSE operation. I suppose it's fair enough that isConnected returns true and isClosed returns false, but why isn't there something like isClosing?

以下是使用 pre-NIO 套接字的测试类.但是使用 NIO 获得了相同的结果.

Below are the test classes that use pre-NIO sockets. But identical results are obtained using 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
...

推荐答案

我经常使用套接字,主要是使用选择器,虽然不是网络 OSI 专家,但据我所知,调用 shutdownOutput() 在 Socket 上实际上发送了一些在网络 (FIN) 上唤醒我的另一端选择器的东西(C 语言中的行为相同).这里有检测:实际检测到尝试时会失败的读取操作.

I have been using Sockets often, mostly with Selectors, and though not a Network OSI expert, from my understanding, calling shutdownOutput() on a Socket actually sends something on the network (FIN) that wakes up my Selector on the other side (same behaviour in C language). Here you have detection: actually detecting a read operation that will fail when you try it.

在您提供的代码中,关闭套接字将关闭输入和输出流,而无法读取可能可用的数据,从而丢失它们.Java Socket.close() 方法执行正常"断开连接(与我最初的想法相反),因为输出流中留下的数据将被发送然后是 FIN 表示关闭.FIN 将被另一方确认,因为任何常规数据包都会1.

In the code you give, closing the socket will shutdown both input and output streams, without possibilities of reading the data that might be available, therefore loosing them. The Java Socket.close() method performs a "graceful" disconnection (opposite as what I initially thought) in that the data left in the output stream will be sent followed by a FIN to signal its close. The FIN will be ACK'd by the other side, as any regular packet would1.

如果需要等待对方关闭它的socket,就需要等待它的FIN.为了实现这一点,您必须检测Socket.getInputStream().read() Socket.getInputStream().read() <0,这意味着你不应该关闭你的套接字,因为它会关闭它的InputStream.

If you need to wait for the other side to close its socket, you need to wait for its FIN. And to achieve that, you have to detect Socket.getInputStream().read() < 0, which means you should not close your socket, as it would close its InputStream.

从我在 C 中所做的,现在在 Java 中,实现这样的同步关闭应该是这样完成的:

From what I did in C, and now in Java, achieving such a synchronized close should be done like this:

  1. 关闭套接字输出(在另一端发送 FIN,这是该套接字将发送的最后一件事).输入仍处于打开状态,因此您可以read() 并检测远程close()
  2. 读取套接字InputStream,直到我们收到来自另一端的回复FIN(因为它会检测到FIN,所以它会经历同样的优雅断开过程).这在某些操作系统上很重要,因为只要其缓冲区之一仍然包含数据,它们实际上就不会关闭套接字.它们被称为幽灵"套接字并用完操作系统中的描述符编号(现代操作系统可能不再是问题)
  3. 关闭套接字(通过调用 Socket.close() 或关闭其 InputStreamOutputStream)
  1. Shutdown socket output (sends FIN on the other end, this is the last thing that will ever be sent by this socket). Input is still open so you can read() and detect the remote close()
  2. Read the socket InputStream until we receive the reply-FIN from the other end (as it will detect the FIN, it will go through the same graceful diconnection process). This is important on some OS as they don't actually close the socket as long as one of its buffer still contains data. They're called "ghost" socket and use up descriptor numbers in the OS (that might not be an issue anymore with modern OS)
  3. Close the socket (by either calling Socket.close() or closing its InputStream or OutputStream)

如以下 Java 片段所示:

As shown in the following Java snippet:

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 循环忙碌(例如,如果发送部分只发送数据,从不读取以检测连接终止.这很笨拙,但您可能无法控制).

Of course both sides have to use the same way of closing, or the sending part might always be sending enough data to keep the while loop busy (e.g. if the sending part is only sending data and never reading to detect connection termination. Which is clumsy, but you might not have control on that).

正如@WarrenDew 在他的评论中指出的那样,丢弃程序(应用层)中的数据会导致应用层非正常断开连接:尽管所有数据都是在 TCP 层接收的(while循环),它们将被丢弃.

As @WarrenDew pointed out in his comment, discarding the data in the program (application layer) induces a non-graceful disconnection at application layer: though all data were received at TCP layer (the while loop), they are discarded.

1:来自Java 基础网络":见图.3.3 p.45,以及整个 §3.7,pp 43-48

1: From "Fundamental Networking in Java": see fig. 3.3 p.45, and the whole §3.7, pp 43-48

这篇关于为什么在不尝试 I/O 的情况下不可能检测到 TCP 套接字被对等端正常关闭?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆