我应该从两端关闭插座吗?

时间:2010-03-16 13:15:48

标签: java sockets networking

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

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

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

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

6 个答案:

答案 0 :(得分:2)

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

如果未正确关闭,套接字将“卡在”“close_wait”中。 即使套接字关闭,它的状态也会在time_wait中持续很短的时间。

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

(因为你正在创建一个完全不同的套接字,因为套接字由remote-ip,远程端口,本地ip和本地端口标识。)

答案 1 :(得分: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 in in use:connect”

答案 2 :(得分:1)

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

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

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

如果使用阻塞I / O,我首先要在套接字上设置读取超时。不幸的是,如果对等体没有响应,则写入没有相应的超时。 我发现在我们的环境中有一个重要的工具是通过java.nio方法创建阻塞套接字,然后以可配置的间隔中断处理线程。

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

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

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

答案 3 :(得分:1)

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

我不担心。

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

答案 4 :(得分:1)

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

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

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

答案 5 :(得分:-1)

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

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

由于 Sunil Kumar Sahoo