TCP握手中的半连接队列和全连接队列

在之前学习TCP的三次握手时发现了一个比较重要的知识点,backlog。但是我没有遇到过。此篇权当是记录知识点以备后续。

流程图:


在握手阶段存在两个队列:syns queue(半连接队列);accept queue(全连接队列)

流程简述如下:

1. 客户端发送SYN付服务端进行第一次报文握手,此时服务端将此请求信息放在半连接队列中并回复SYN+ACK给客户端。

此处就是SYN Flood(后续关注,此篇记录)攻击的点,攻击方要做的就是不停的建立连接,但是确不给出ACK确认,导致半连接队列满了,其他请求无法进入。

2. 客户端收到SYN+ACK,随机发出ACK确认给服务端;

    全队列未满:从半连接队列拿出此消息放入全队列中。

    全队列已满:处理方式和tcp_abort_on_overflow(cat /proc/sys/net/ipv4/tcp_abort_on_overflow)有关:

                      tcp_abort_on_overflow=0;表示丢弃该ACK;

                      tcp_abort_on_overflow=1;表示发送一个RST给客户端,直接废弃掉这个握手过程。

3. 服务端accept处理此请求,从全队列中将此请求信息拿出。

backlog:

backlog表明它是已连接但是还未处理的队列的大小。这个队列如果满的话,会发送一个ECONNREFUSED错误信息给客户端。就是常见的“Connection Refused”。

简单测试:使用Java内的socket测试:

客户端:

    private static Socket[] clients = new Socket[30];

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            clients[i] = new Socket("127.0.0.1", 8888);
            System.out.println("Client:" + i);
        }
    }

服务端1:

    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(8888, 5);

        while (true) {
            // server.accept();
        }
    }

输出:

Client:0
Client:1
Client:2
Client:3
Client:4
Exception in thread "main" java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at java.net.Socket.connect(Socket.java:538)
	at java.net.Socket.<init>(Socket.java:434)
	at java.net.Socket.<init>(Socket.java:211)
	at Test1.main(Test1.java:15)

源码简析:从下面的源码内可以看见backlog提供了一个类似容量的限制,小于1或者不传默认值是50。因此在只提供了5个backlog大小的测试中,因为前5个占据了全连接队列没有被处理,第六个进来时,没有位置,所以拒绝了(从此处可以看见参数tcp_abort_on_overflow=1,0的话会直接丢弃)

    public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
        setImpl();
        if (port < 0 || port > 0xFFFF)
            throw new IllegalArgumentException(
                       "Port value out of range: " + port);
        if (backlog < 1)
          backlog = 50;
        try {
            bind(new InetSocketAddress(bindAddr, port), backlog);
        } catch(SecurityException e) {
            close();
            throw e;
        } catch(IOException e) {
            close();
            throw e;
        }
    }
服务端2:(注释打开)
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(8888, 5);

        while (true) {
             server.accept();
        }
    }

输出:

Client:0
Client:1
Client:2
Client:3
Client:4
Client:5
Client:6
Client:7
Client:8
Client:9
此时由于accept处理的存在,使得全连接队列有位置,所以能够处理完所有的请求。

全连接队列的大小:

Java的Socket的默认backlog大小是50;(看上面源码分析)

Tomcat的默认backlog大小是100;(看8080端口)


可以自由配置

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxThreads="800" acceptCount="1000"/> 

AliTomcat默认backlog值为200;(参考别的连接)

Nginx的默认backlog值是511;

全队列的大小和backlog有关,但是未必是backlog的值,是由backlog和somaxconn(系统参数)两个值中的较小值决定。

cat /proc/sys/net/core/somaxconn

此处我贴一下linux下的大小是128,如果我们传进来的值是50,那么全队列大小就是50。

半连接队列的大小:

半连接的大小取决于64和tcp_max_syn_backlog的较大值。

cat /proc/sys/net/ipv4/tcp_max_syn_backlog 
本机的值是256。

通过ss查看Socket的状态


基于上面的测试例子中的Java代码,这次backlo的值设置为10,作为明显区分。


Send-Q表示的就是当前端口的全连接队列大小,Recv-Q表示的是全连接队列使用的多少。


针对8888端口:客户端只循环了5次,所以Recv-Q的使用量只有5个,而全连接队列的大小是10。

先记录这么多,因为没有什么实际的问题作为继续研究的样例,所以没什么再深入的学习了。

贴一下参考博客:

阿里中间件

猜你喜欢

转载自blog.csdn.net/qq_32924343/article/details/80804242