java – 为什么不可能,没有尝试I / O,检测TCP套接字被正常关闭由对等?

作为一个recent question的后续,我想知道为什么在Java中不可能没有尝试在TCP套接字上读/写,以检测套接字已被对等体正常关闭?这似乎是这样的情况,无论是使用pre-NIO Socket还是NIO SocketChannel。

当对等体正常关闭TCP连接时,连接两侧的TCP堆栈知道事实。服务器端(启动关闭的那个)最终处于状态FIN_WAIT2,而客户端(没有明确响应关闭的那个)最终处于状态CLOSE_WAIT。为什么在Socket或SocketChannel中没有一个方法可以查询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
...
我经常使用套接字,大多是选择器,虽然不是一个网络OSI专家,从我的理解,调用shutdownOutput()在Socket实际上发送的东西在网络(FIN),唤醒我的选择器在另一边C语言中的行为)。这里你有检测:实际检测读操作,将失败,当你尝试它。

在您给出的代码中,关闭套接字将关闭输入和输出流,无法读取可用的数据,因此丢失它们。 Java Socket.close()方法执行“优雅”断开(与我最初想象的相反),因为输出流中剩余的数据将被发送,后面跟着一个FIN来发送它的close。 FIN将被另一方ACK,正如任何常规分组将1。

如果你需要等待另一边关闭它的套接字,你需要等待它的FIN。并且要实现,你必须检测Socket.getInputStream()。read()< 0,这意味着你不应该关闭你的套接字,因为它会关闭它的InputStream。 从我在C中,现在在Java,实现这样的同步关闭应该这样做:
>关闭套接字输出(在另一端发送FIN,这是这个套接字发送的最后一个东西)。输入仍然打开,因此您可以读取()并检测远程关闭()
>读取套接字InputStream,直到我们从另一端接收到回复FIN(因为它会检测到FIN,它将通过相同的优美的连接过程)。这在一些操作系统上很重要,因为它们实际上并不关闭套接字,只要它的一个缓冲区仍然包含数据。它们被称为“ghost”套接字,并在操作系统中使用描述符编号(这可能不是现代操作系统的问题)
>关闭套接字(通过调用Socket.close()或关闭它的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:从“Fundamental Networking in Java”:见图。 3.3 p.45,以及整个§3.7,第43-48页

http://stackoverflow.com/questions/155243/why-is-it-impossible-without-attempting-i-o-to-detect-that-tcp-socket-was-grac

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:java – 为什么不可能,没有尝试I / O,检测TCP套接字被正常关闭由对等?