• 浅谈 Java Socket 构造函数参数 backlog


    ServerSocket API

    API:java.net.ServerSocket 1.0

    • ServerSocket(int port, int backlog)
      创建一个监听端口的服务器套接字
    • ServerSocket() 1.4
      创建一个未绑定的服务器套接字
    • void bind(SocketAddress endpoint, int backlog) 1.4
      把服务器套接字绑定到指定套接字地址上

    注意
    bind 方法常常在调用无参构造函数 ServerSocket() 之后使用。如果 bind 和其他有参构造函数一起使用,会产生报错。以下错误示范:

    ServerSocket serverSocket = new ServerSocket(8081, 2); // 使用有参构造函数,创建一个监听端口的服务器套接字
    serverSocket.bind(new InetSocketAddress(8081), 1); // 这里再调用bind方法,重复绑定,产生异常!
    

    如下图所示:
    SocketException-already bound

    backlog 参数

    backlog 参数是套接字上请求的最大挂起连接数。它的确切语义是特定于实现的。
    backlog 是请求的 incoming 连接队列的最大长度。

    创建 ServerSocket 并绑定端口:

    Socket 服务端代码

    public class SocketServer {
    
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080), 1);
            System.in.read(); // 不让服务端关闭
        }
    }
    

    这里使用 telnet 127.0.0.1 8080 打开两个 Windows Telnet 客户端,根据 WireShark 的抓包结果如下:

    backlog

    • 端口号为 5608 的第一个 Telnet 客户端,经过三次握手,顺利地和服务器建立的连接并保持了连接
    • 端口号为 5620 的第二个 Telnet 客户端,首先发送了第一次握手报文(SYN),但是服务器因为设置了 backlog 为 2,因此直接给客户端返回 (RST) 报文。客户端尝试重传 (报文内容和第一次握手时的报文一模一样),尝试2次后收到的仍然是RST 报文,就不了了之。

    如果改用 Java 客户端,代码如下:

    public class Clients {
    
        public static void main(String[] args) throws IOException {
            Socket[] clients = new Socket[2];
            for (int i = 1; i <= clients.length; i++) {
                clients[i-1] = new Socket("127.0.0.1", 8080);
                System.out.println("client connection:" + i);
            }
        }
    }
    

    控制台发生报错:
    Connection refused

    • 第一个客户端 Socket 创建成功,但是第二个客户端的 Socket 被拒绝连接。

    因此,在这种情况下,能够成功创建客户端套接字的个数,刚好就是创建 ServerSocket 时候指定的 backlog 的数量。

    用 accept 返回 Socket 对象

    API:java.net.ServerSocket 1.0

    • Socket accept()
      等待连接。该方法阻塞(即使之空闲)当前线程直到建立连接为知。该方法返回一个 Socket 对象,程序可以通过这个对象与连接中的客户端进行通信。

    我们改造一下 ServerSocket,在 while 循环调用 ServerSocket#accept 方法。

    public class SocketServer {
    
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080), 1);
            int acceptCount = 0;
            while (true) {
                Socket clientSocket = serverSocket.accept();
                InetSocketAddress remote = (InetSocketAddress) clientSocket.getRemoteSocketAddress();
                System.out.println(remote.getPort());
                ++acceptCount;
                System.out.println("当前客户端连接数:" + acceptCount);
            }
        }
    }
    

    客户端我们也改一下,变成并发量 100 的连接请求

    public class Clients {
    
        public static void main(String[] args) throws IOException {
            Socket[] clients = new Socket[100];
    
            for (int i = 1; i <= clients.length; i++) {
                final int index = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            clients[index-1] = new Socket("127.0.0.1", 8080);
                            System.out.println("client connection:" + index);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }, String.valueOf(i)).start();
            }
            System.in.read();
        }
    }
    

    经过实验,backlog=1 时, 一次运行结果如下:

    多次执行,拒绝连接的数量存在波动。

    给服务端加上阻塞

    上一个实验中,我们使用 accept 来返回 Socket 对象。我们把套接字从 sync_queue 转移到 accept_queue,这样就可以接收更多的连接了。

    但是,如果我们用 sleep 来模拟接收到连接后的收发消息,业务处理的延迟,实验结果又会不同。

    带延迟的 SocketServer

    public class SocketServer {
    
        public static void main(String[] args) throws IOException, InterruptedException {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080), 1);
            int acceptCount = 0;
            while (true) {
                Socket clientSocket = serverSocket.accept();
                InetSocketAddress remote = (InetSocketAddress) clientSocket.getRemoteSocketAddress();
                System.out.println(remote.getPort());
                ++acceptCount;
                System.out.println("当前客户端连接数:" + acceptCount);
                Thread.sleep(2000); // 加入延迟时间
            }
        }
    }
    

    同步客户端

    public class SyncClients {
    
        private static Socket[] clients = new Socket[100];
        public static void main(String[] args) throws Exception {
            for (int i = 1; i <= clients.length; i++) {
                clients[i-1] = new Socket("127.0.0.1", 8080);
                System.out.println("client connection:" + i);
            }
        }
    }
    

    同步连接客户端,套接字是一个接着一个连接的。先完成一组三次握手,再进行第二组三次握手,以此类推。
    第一次连接从 sync_queue 转移到 accept_queue,
    第二次连接进入到 sync_quque,
    第三次连接因为 backlog=1 的缘故,被拒绝连接了,客户端抛出异常。
    结果如图所示:

    异步客户端

    public class Clients {
        static Socket[] clients = new Socket[3];
        public static void main(String[] args) throws IOException {
    
            for (int i = 0; i < clients.length; i++) {
                final int index = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            clients[index] = new Socket("127.0.0.1", 8080);
                            System.out.println("client connection:" + index);
                            Thread.sleep(10000);
                        } catch (IOException | InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, String.valueOf(i)).start();
            }
            System.in.read();
        }
    }
    

    测试结果并没有像我预料的那样,第三次连接失败

    这里我先留个坑,跟网上查询的 sync_queue 理论有些不符合的样子。如果有熟悉底层的大佬可以指点一二。

    小贴士

    • 如果 SyncClients 中没有加入 System.in.read() 代码,客户端程序会停止运行,客户端主动给服务器端发送 RST 报文重置连接。

    参考博客

    浅谈tcp socket的backlog参数

  • 相关阅读:
    学习日记16、easyui editor datagrid 动态绑定url
    学习日记15-1布局页同时引用多个model
    phpstrom的find in path 搜索失效
    markdown画流程图-mermaid工具
    Windows下PHP安装 Imagick扩展
    ftp实现通过数据库的虚拟用户认证
    PHP报错Only variables should be passed by reference in的解决方法
    ffmpeg总结
    PHP执行外部命令总结(exec、system、passthru、shell_exec)
    phpstorm配置使用xdebug
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/java-serversocket-backlog.html
Copyright © 2020-2023  润新知