代码之家  ›  专栏  ›  技术社区  ›  Alexander

为什么不尝试I/O就无法检测到TCP套接字已被对等方正常关闭?

  •  88
  • Alexander  · 技术社区  · 17 年前

    作为 recent question Socket 或者NIO SocketChannel .

    FIN_WAIT2 ,而客户端(不显式响应关闭的客户端)最终处于状态 CLOSE_WAIT . 为什么没有方法 插座 嵌退火 它可以查询TCP堆栈以查看底层TCP连接是否已终止?是不是TCP堆栈没有提供这样的状态信息?或者这是一个设计决策,以避免对内核的昂贵调用?

    在已经发布了这个问题的一些答案的用户的帮助下,我想我看到了这个问题可能来自哪里。未显式关闭连接的一端最终处于TCP状态 关闭/等待 CLOSE 操作。我想这很公平 isConnected 回报 true isClosed 回报 false isClosing ?

    下面是使用前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
    ...
    
    12 回复  |  直到 9 年前
        1
  •  29
  •   Matthieu    9 年前

    据我所知,我经常使用socket,主要使用选择器,虽然不是网络OSI专家 shutdownOutput() 在一个Socket上,实际上是在网络(FIN)上发送一些东西,唤醒另一边的选择器(C语言中的相同行为)。给你 侦查

    在您给出的代码中,关闭套接字将关闭输入流和输出流,而不可能读取可能可用的数据,因此将其丢失。爪哇 Socket.close() 方法执行“优雅”断开连接(与我最初的想法相反),因为输出流中留下的数据将被发送 后面跟着鳍 以表示它关闭了。鱼鳍会被另一边确认,就像任何普通的包裹一样 .

    如果你需要等待另一边关闭它的插座,你需要等待它的鳍。为了达到这个目的,你 不得不 发现 Socket.getInputStream().read() < 0 ,也就是说你应该 按原样关上插座 关闭its InputStream .

    1. 关闭套接字输出(在另一端发送FIN,这是这个套接字最后发送的内容)。输入仍处于打开状态,因此您可以 read() 检测遥控器 close()
    2. 读取套接字 输入流 直到我们从另一端收到回复FIN(因为它将检测到FIN,它将经历同样优美的断开连接过程)。这在一些操作系统上很重要,因为只要其中一个缓冲区仍然包含数据,它们就不会真正关闭套接字。它们被称为“幽灵”套接字,并在操作系统中耗尽描述符编号(这可能不再是现代操作系统的问题)
    3. 关闭套接字(通过调用 套接字。关闭() 或者关闭它 输入流 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层( 循环),它们被丢弃。

    1个 :从“ Fundamental Networking in Java

        2
  •  18
  •   dty    13 年前

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

    Wikipedia

    TCP提供可靠、有序的 一台计算机上的程序到另一台计算机 另一台计算机上的程序。

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

    “接近”一词也具有误导性。只有FIN声明,意思是“我不会再给你寄东西了”,但这并不意味着航班上没有包裹,也不意味着另一个没有什么可说的。如果您将snail mail作为数据链路层实现,或者如果您的数据包经过不同的路由,则可能是接收器接收的数据包顺序错误。TCP知道如何为您解决这个问题。

    另外,作为一个程序,您可能没有时间继续检查缓冲区中的内容。所以,在你方便的时候,你可以检查一下缓冲器里有什么。总之,当前的socket实现还不错。如果真的有isPeerClosed(),那就是每次要调用read时必须进行的额外调用。

        3
  •  11
  •   Mike Dimmick    17 年前

    底层sockets API没有这样的通知。

        4
  •  8
  •   Alexander    17 年前

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

    当TCP连接建立并且一个对等调用时 close() shutdownOutput() 在其套接字上,连接另一侧的套接字转换为 CLOSE_WAIT 国家。原则上,可以从TCP堆栈中找出套接字是否在 关闭/等待 无需调用的状态 read/recv (例如。, getsockopt() 在Linux上: http://www.developerweb.net/forum/showthread.php?t=4395 ),但那不是便携式的。

    爪哇的 Socket 类似乎被设计为提供与BSD TCP套接字类似的抽象,这可能是因为这是人们在编程TCP/IP应用程序时所习惯的抽象级别。BSD套接字是一种通用的支持套接字,而不仅仅是INET(例如,TCP)套接字,因此它们不提供一种可移植的方法来查找套接字的TCP状态。

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

        5
  •  7
  •   Uncle Per    15 年前

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

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

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

        6
  •  4
  •   Sean Sean    17 年前

    当Java IO堆栈在突然的拆卸中被破坏时,它肯定会发送FIN。你无法检测到这一点是毫无意义的,大多数客户机只有在关闭连接时才会发送FIN。

        7
  •  4
  •   user207421    12 年前

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

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

    另一个问题是实现说这个行为是可选的。当Java努力实现可移植性时,它们实现了最好的公共特性。我想,维护(操作系统,半双工实现)的映射可能是个问题。

        8
  •  3
  •   Blazor Joshua    12 年前

    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 */  
    }  
    
        9
  •  2
  •   Dean Hiller    14 年前

    这是一个蹩脚的解决办法。使用SSL;)并且SSL在拆卸时执行关闭握手,因此您将收到关闭套接字的通知(大多数实现似乎执行propert handshake-teardown,即)。

        10
  •  2
  •   user207421    12 年前

    这种行为(不是Java特有的)的原因是您没有从TCP堆栈中获得任何状态信息。毕竟,socket只是另一个文件句柄,如果不尝试( select(2) 不会有帮助的,这只是一个信号,你可以尝试没有阻碍)。

    有关详细信息,请参见 Unix socket FAQ .

        11
  •  0
  •   Ray    17 年前

    只有写操作需要交换数据包,这样才能确定连接的丢失。一个常见的解决方法是使用KEEP ALIVE选项。

        12
  •  -3
  •   JimmyB    14 年前