美文网首页
【转】TCP半连接和全连接队列

【转】TCP半连接和全连接队列

作者: 菜鸟小玄 | 来源:发表于2018-08-13 16:39 被阅读0次

在握手阶段存在两个队列: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测试:

客户端:

privatestaticSocket[] clients =newSocket[30];

publicstaticvoidmain(String[] args)throwsException{

for(inti =0; i <10; i++) {

clients[i] =newSocket("127.0.0.1",8888);

System.out.println("Client:"+ i);

        }

    }

服务端1:

publicstaticvoidmain(String[] args)throwsException{

ServerSocket server =newServerSocket(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.(Socket.java:434)

at java.net.Socket.(Socket.java:211)

at Test1.main(Test1.java:15)

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

publicServerSocket(intport,intbacklog, InetAddress bindAddr)throwsIOException{

        setImpl();

if(port <0|| port >0xFFFF)

thrownewIllegalArgumentException(

"Port value out of range: "+ port);

if(backlog <1)

backlog =50;

try{

bind(newInetSocketAddress(bindAddr, port), backlog);

}catch(SecurityException e) {

            close();

throwe;

}catch(IOException e) {

            close();

throwe;

        }

    }

服务端2:(注释打开)

publicstaticvoidmain(String[] args)throwsException{

ServerSocket server =newServerSocket(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端口)

可以自由配置

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。

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

队满时会发生什么

Q:为什么 有的应用服务器进程,会单独使用一个线程去调用accept来建立服务端和客户端的连接,比如tomcat;而有的则是一个线程做所有的事,即获取连接后还会做I/O等其他操作?

A:首先SYN队列和ACCEPT队列都不是无限大小的,上面已经提到。既然队列长度是有限的,那就有满的时候。比如当上图中步骤1的执行速度大于第2步执行的速度,SYN队列就会不断增大,直到队列满;第2步的执行速度远大于第3步的执行速度,ACCEPT队列同样会满。第1、2步不是程序可控的,第3步是程序的行为。

先看第一种情况:SYN队列满。如果SYN队列满,则会直接丢弃连接请求。 

比如syn floods 攻击就是针对半连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,server需要一个超时时间把这个连接断开,否则大量这样的连接导致server上这个队列满其它正常请求无法进来。

第二种情况:ACCEPT队列满。这种情况貌似比较复杂,我查了学长的博客,发现学长讨论过这个问题。accept队列满的讨论 

如果ACCEPT队列满了,server 通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 来决定如何返回:

tcp_abort_on_overflow 为 0,不会把连接从SYN队列中移除,server过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),这样来回重发几次,次数由 /proc/sys/net/ipv4/tcp_synack_retries(centos默认为 5 ) 指定,如果三次握手第三步的时候 ACCEPT 队列一直是满,那么server扔掉client 发过来的 ACK(在server端认为连接还没建立起来);

tcp_abort_on_overflow 为 1 表示第三步的时候如果 ACCEPT 队列满了,server发送一个RST包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来),客户端会出现 connection reset by peer 的异常。

PS:还有一个事实是,一般 overflowed 表示全连接队列溢出次数,socket ignored 表示半连接队列溢出次数,这两个是同步增加的,overflow的时候一定会socket ignored++。

所以对应用服务器进程来说,如果ACCEPT队列中有已经建立好的TCP连接,却没有及时的把它取出来,这样一旦导致两个队列满后就会使客户端不能再建立新的连接,引发严重问题。所以,比如tomcat等服务器会用独立的线程只做accept获取连接这一件事,以防止不能及时的去accept获取连接。

相关文章

网友评论

      本文标题:【转】TCP半连接和全连接队列

      本文链接:https://www.haomeiwen.com/subject/wmuubftx.html