【问题标题】:Should I close sockets from both ends?我应该从两端关闭套接字吗?
【发布时间】:2010-03-16 13:15:48
【问题描述】:

我有以下问题。我的客户端程序监视本地网络中服务器的可用性(使用 Bonjour,但它并不重要)。一旦客户端应用程序“注意到”服务器,客户端就会尝试创建一个套接字:Socket(serverIP,serverPort);

在某些时候,客户端可能会丢失服务器(Bonjour 说服务器在网络中不再可见)。因此,客户端决定close 套接字,因为它不再有效。

在某个时刻,服务器再次出现。因此,客户端尝试创建一个与此服务器关联的新套接字。但!服务器可以拒绝创建此套接字,因为它(服务器)已经有一个与客户端 IP 和客户端端口相关联的套接字。发生这种情况是因为套接字是由客户端关闭的,而不是由服务器关闭的。会发生吗?如果是这样的话,这个问题怎么解决?

好吧,我知道客户端不太可能尝试从同一端口(客户端端口)连接到服务器,因为客户端会随机选择其端口。但它仍然可能发生(只是偶然)。对吧?

【问题讨论】:

    标签: java sockets networking


    【解决方案1】:

    是的,一旦检测到故障就关闭套接字。

    如果未正确关闭,套接字将“卡”在“close_wait”中。 即使套接字关闭,它的状态也会在 time_wait 中短暂停留。

    但是,如果您将应用程序设计为对每个新连接使用不同的本地端口,则无需等待旧套接字关闭。

    (当您创建一个完全不同的套接字时,因为套接字由远程 IP、远程端口、本地 ip 和本地端口标识。)

    【讨论】:

    • '如果您将应用程序设计为对每个新连接使用不同的本地端口'因为这是自动发生的,因此无需为此进行设计。在构建 Sockets 时完全避免指定本地端口。
    • 哦,是的,我的意思是,不应将应用程序设计为依赖于请求来自哪个端口。
    【解决方案2】:

    为什么不能发生这种情况的快速/肮脏的说明(注意客户端在其连接中强制使用相同的本地端口):

    public class Server{
    
    public static void main(String[] args) throws Exception {
        new Thread(){
            java.net.ServerSocket server = new java.net.ServerSocket(12345);
            java.util.ArrayList<java.net.Socket> l = new java.util.ArrayList<java.net.Socket>();
            public void run() {
                try{
                while(true){
                     java.net.Socket client = server.accept();
                    System.out.println("Connection Accepted: S: "+client.getLocalPort()+", C: "+client.getPort());
                    l.add(client);
                }
                }catch(Exception e){e.printStackTrace();}
            }
        }.start();
    }
    

    和一个客户端(用有效的东西替换服务器地址):

    import java.net.InetAddress;
    import java.net.Socket;
    
    
    public class SocketTest {
        public static void main(String[] args) throws Exception {
            InetAddress server = InetAddress.getByName("192.168.0.256");
            InetAddress localhost = InetAddress.getLocalHost();
            Socket s = new Socket(server, 12345, localhost, 54321);
            System.out.println("Client created socket");
            s.close();
            s = null;
            System.gc();
            System.gc();
            Thread.sleep(1000);
            s = new Socket(server, 12345, localhost, 54321);
            System.out.println("Client created second socket");
            s.close();
            System.exit(55);
        }
    }
    

    如果您启动服务器然后尝试运行客户端,第一个连接将成功,但第二个连接将失败并出现“java.net.BindException: Address already in use: connect”

    【讨论】:

      【解决方案3】:

      简短回答:是的,您应该关闭两端的套接字。

      虽然答案很简单,但实际上,如果您不在客户端-服务器协议中构建一些 ACK/NACK 方案,则可能很难检测到对等方已停止响应。

      即使使用您的协议 ACK,您的处理线程也可能会挂起等待永远不会来自客户端的 ACK,反之亦然。

      如果您使用阻塞 I/O,我将首先在套接字上设置读取超时。不幸的是,如果对等端变得无响应,则写入没有相应的超时。 我发现在我们的环境中有价值的一种钝器是通过 java.nio 方法创建阻塞套接字,然后以可配置的时间间隔中断处理线程。

      中断处理线程将关闭套接字,但如果你选择足够大的超时时间,你就会知道有问题。我们选择这种方法是因为应用程序最初是使用阻塞 I/O 编写的,并且将其转换为非阻塞的成本非常高。

      不过,使用非阻塞 I/O,您可以更细粒度地检查连接状态,并对缓慢/无响应的连接做出更智能的反应。

      虽然非阻塞 I/O 需要更高的前期投资,但我认为它会在以后的可靠性和吞吐量方面带来更好的回报。

      【讨论】:

        【解决方案4】:

        客户端操作系统不会这么快就将同一个端口分配给一个新的套接字。有几种机制可以防止它。其中之一是 TIME_WAIT 状态,在连接关闭后保留端口一段时间。

        我不会担心的。

        如果您确实需要检测断开连接,则必须实现由客户端和服务器发起的 ping/pong 协议。

        【讨论】:

          【解决方案5】:

          听起来您的客户端正在检测与服务器的连接丢失(使用 Bonjour),但您在另一个方向上没有相应的功能。

          您当然也希望服务器端的非活动连接有某种超时,否则死连接将永远存在。除了您提到的潜在 IP 地址/端口# 冲突问题之外,还有一个事实是死连接正在消耗操作系统和应用程序资源(例如打开的文件描述符)

          相反,当 Bonjour 说服务不再可见时,您可能还想考虑在从客户端关闭连接时不要过于激进。如果您处于无线场景中,那么短暂的连接丢失并不少见,并且 TCP 连接可能会在连接恢复后保持打开和有效(假设客户端仍然具有相同的 IP 地址)。最佳策略取决于您所谈论的连接类型。如果它是一个相对无状态的连接,其中丢弃连接和重试的成本很低(如 HTTP),那么在出现问题的第一个迹象时丢弃连接是有意义的。但是,如果它是具有重要用户状态的长期连接(例如 SSH 登录会话),那么更加努力地保持连接处于活动状态是有意义的。

          【讨论】:

            【解决方案6】:

            如果仅在阻塞套接字的情况下关闭服务器套接字,则客户端套接字将被关闭,反之则不会。

            否则两端的插座会更好。因为套接字对您的系统来说是一个沉重的负担。它将永远使用您系统的本地端口和远程端口。

            谢谢 苏尼尔·库马尔·萨胡

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-06-18
              • 2018-07-02
              • 2013-02-07
              • 1970-01-01
              • 2017-09-01
              相关资源
              最近更新 更多