gpt4 book ai didi

java - 为什么不尝试 I/O 就不可能检测到 TCP 套接字已被对等方优雅地关闭?

转载 作者:IT老高 更新时间:2023-10-28 11:35:16 25 4
gpt4 key购买 nike

作为 recent question 的后续行动,我想知道为什么在 Java 中,如果不尝试在 TCP 套接字上读/写,就不可能检测到套接字已被对等方优雅地关闭?无论是使用 pre-NIO Socket 还是 NIO SocketChannel,似乎都是这种情况。

当对等端优雅地关闭 TCP 连接时,连接两端的 TCP 堆栈都知道这一事实。服务器端(启动关闭的那个)最终处于状态 FIN_WAIT2,而客户端(没有明确响应关闭的那个)最终处于状态 CLOSE_WAIT 。为什么 SocketSocketChannel 中没有可以查询 TCP 堆栈以查看底层 TCP 连接是否已终止的方法?是不是 TCP 栈没有提供这样的状态信息?还是避免对内核进行昂贵的调用是一项设计决策?

在已经发布了这个问题的一些答案的用户的帮助下,我想我知道问题可能来自哪里。没有明确关闭连接的一方最终处于 TCP 状态 CLOSE_WAIT 意味着连接正在关闭并等待一方发出自己的 CLOSE 操作。我想 isConnected 返回 trueisClosed 返回 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
...

最佳答案

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

在您提供的代码中,关闭套接字将关闭输入和输出流,无法读取可能可用的数据,因此会丢失它们。 Java Socket.close()方法执行“优雅”断开连接(与我最初的想法相反),因为留在输出流中的数据将被发送随后是 FIN 以表示其关闭。 FIN 将被对方确认,就像任何常规数据包都会1

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

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

  1. 关闭套接字输出(在另一端发送 FIN,这是此套接字将发送的最后一件事)。输入仍处于打开状态,因此您可以 read()并检测远程close()
  2. 读取套接字 InputStream直到我们收到来自另一端的回复 FIN(因为它会检测到 FIN,它会经历同样的优雅断开过程)。这在某些操作系统上很重要,因为只要其中一个缓冲区仍然包含数据,它们实际上就不会关闭套接字。它们被称为“幽灵”套接字,并在操作系统中耗尽了描述符编号(现代操作系统可能不再是问题)
  3. 关闭套接字(通过调用 Socket.close() 或关闭其 InputStreamOutputStream)

如以下 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:来自“Fundamental Networking in Java”:见图。 3.3 p.45,以及整个 §3.7,第 43-48 页

关于java - 为什么不尝试 I/O 就不可能检测到 TCP 套接字已被对等方优雅地关闭?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/155243/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com