TCP半连接和全连接队列

经过之前对内核源码的阅读,已经简单了解了 linux 内核是如何管理 tcp 连接信息的。当然,之前把重点放在了三次握手的全过程,忽略了不少细节,这篇文章来详细讨论下两个概念:“半连接队列” 全连接队列

同样,本文还是基于 linux-4.18.1的版本。

三次握手过程

看下详细过程:

第一次握手:

调用 connect 请求连接,客户端向服务端发送 SYN 包,并设置 sock 状态 SYN_SENT

第二次握手:

服务端接收到 SYN 报文后,新建一个 request_sock 对象,用来表示这个 半连接,同时将这个 sock 设置为 NEW_SYN_RECV 状态,并添加到 ehash 中,更新半连接数 qlen++, 最后向客户端响应 SYN + ACK

第三次握手:

客户端收到第二次握手包后,向服务端发送 ACK 报文;

服务端接收到 ACK 报文后,新建一个 tcp_sock 对象表示这个全连接,设置状态为 SYN_RECV, 然后将它添加到 ehash中;此外,将代表半连接的那个 request_sock 对象从 ehash 中移除,添加到 全连接队列 中,然后更新半连接数 qlen-- 和 全连接数 sk_ack_backlog++,最后将代表全连接的 sock 状态修改为 ESTABLISHED

通过以上流程可以看到,在 linux 4.18 这个版本中,并不存在所谓的 半连接队列,表示半连接状态的 requst_sock 保存在全局的 ehash 中, 具体结构可以参考文章 TCP源码浅读(1)数据结构,而且它的状态也不是 SYN_RECV,是 NEW_SYN_RECV。这个变化是 linux 4.4 以后产生的,可以阅读下这篇文章 TCP_NEW_SYN_RECV

此外,全连接队列 保存的也不是表示全连接状态的 tcp_sock,而是表示半连接的 requst_sock ,只是每个 requst_sock 又关联到了 tcp_sock

本文结合源码来解析下 半连接数全连接数 是如何影响服务器接收新的请求连接的。

半连接

怎么查看半连接数?

可以通过 netstat 命令查看当前处于半连接状态的连接个数:

半连接数最大值

linux 不同的版本对 半连接数最大值 的定义有所不同,此外,服务端支持的最大半连接数还受内核参数的限制。在 TCP源码浅读(4)三次握手-被动方接收SYNTCP源码浅读(4)三次握手-被动方接收SYN 中分析过服务端接收 SYN 的流程,服务端接收到客户端的 SYN 请求包后,会创建一个 request_sock 对象表示这个半连接,而在创建这个对象之前,服务端会首先检查半连接数是否已经达到最大值,看下实现:

暂时忽略 sysctl_tcp_syncookies 这个值的影响,画个流程图: 

从源码流程我们可以看到,服务端支持的最大半连接数受到三个参数的限制:

1、半连接数最大值的限制;

2、全连接数最大值的限制;

3、内核参数 /proc/sys/net/ipv4/tcp_max_syn_backlog 的限制。

在 linux 4.18.1 的这个版本中, 半连接数最大值和全连接数最大值一样,都等于 min(backlog, somaxconn),

  • backlog: listen 函数第二个参数
  • somaxconn: 内核参数,/proc/sys/net/core/somaxconn

而条件三则限制了最大半连接数要小于 3/4 * tcp_max_syn_backlog

做两个模拟测试验证下

模拟测试一

写个简单的服务器程序,设置 backlog = 128: listen(listenfd, 128);

查看 somaxconn tcp_max_syn_backlog

可以得到 半连接数 和 全连接数 最大值都等于 min(somaxconn, backlog) = 128;

而达到以上 第三点 导致连接请求被丢弃的大小为 3/4 * tcp_max_syn_backlog = 3/4 * 1024 = 768

因此,该服务程序支持的最大半连接数应该是 128 。

接下来我们用 hping3 命令测试下,受机器限制,本次测试服务器程序 和 hping3 程序在同一台机器,端口随便起个 9090 。

查看当前半连接数,你会发现,它始终不会超过 128 (半连接数最大值得判断是大于等于就丢弃)。

 

模拟测试二

现在我们修改下参数,看下满足条件三的效果

再次执行同样的测试,你会发现这一次 半连接数始终是 61 (条件三判断是大于,所以要加1)。

 

此外,我们还可以通过以下方式判断 半连接数 是否已经达到服务端支持的最大值了:  

达到最大值后怎么处理?

比较常见的有三种方案:

1、增大服务器支持的最大半连接数(要同时修改三个参数);

2、减小 SYN + ACK 重传次数,加速半连接状态连接的死亡;

3、设置 tcp_syncookies 参数,直接不使用半连接队列。

1、增大服务器支持的最大半连接数

如果真的是并发请求量大,我们可以修改相应参数,增大服务器支持的最大半连接数。从源码分析我们看到要同时修改以下三个参数才会有效:

  • 服务器程序 listen() 函数中的第二个参数值 backlog;
  • 内核参数 /proc/sys/net/core/somaxconn
  • 内核参数 /proc/sys/net/ipv4/tcp_max_syn_backlog

2、减小 SYN + ACK 的重传次数。

服务端收到第三次握手后,会将半连接状态的 requset_sock 对象从 ehash 中移除,同时,更新半连接数 qlen-- ;而在一定时间内如果一直没有收到第三次握手的响应包,就会向客户端重传 SYN + ACK 包,一直到收到第三次握手或重传次数达到最大值后才会断开连接:

在正常的连接请求过程中,重传次数越多,成功连接建立的概率就越大;

但是如果遇到 SYN 攻击,这个值越小就越好,因为它可以加速连接状态的死亡,减轻半连接的无效占用。

3、开启 tcp_syncookies

从前面的源码可以看出,在开启 tcp_syncookies 之后,条件一 和 条件三 将直接失效,此时,服务端支持的最大半连接数仅受 全连接最大值 的限制。

/proc/sys/net/ipv4/tcp_syncookies 参数有三个选项值:

  • 0 ,关闭该功能;
  • 1 ,当 SYN 半连接队列放不下时,启用;
  • 2 ,无条件开启功能。

当然,在开启 tcp_syncookies 后,因为没有将这个半连接保存到全局信息表中,那在服务端在收到第三次握手包后是怎么获取连接信息的呢?

服务器收到 SYN 报文后,将 连接信息编码放在 ISN 中, 随着 SYN+ACK 响应报文发送给客户端,不用将这个连接加到半连接队列

客户端返回 ACK 报文时要同时带上这个编码值,服务端收到响应后,取出该编码值,对其进行反算校验,如果验证通过,就可以成功建立连接,加入到全连接队列。

全连接队列

全连接队列长度查看

全连接队列 也叫 accept 队列 ,用来存放处于 ESTABLISHED 状态的连接,等待程序调用 accept 函数将连接从队列取走。

可以用 ss 命令 来查看 当前全连接队列大小全连接队列最大值

使用该命令查看全连接队列信息的时候一定要使用 -l 选项,表示查看处于 监听状态 下的连接信息:

  • Recv-Q 当前全连接队列大小
  • Send-Q:全连接队列最大值

如果不指定监听状态,Recv-QSend-Q 表示的含义为:

  • Recv-Q:已收到但未被应用程序读取的字节数;
  • Send-Q:已发送但未收到确认的字节数;

全连接队列最大值

看下 linux-4.18.1 版本的实现

可以看到,全队列最大值等于 min(backlog, somaxconn)

而全连接数的检查在 TCP源码浅读(6)三次握手-被动方接收ACK已经看到过,服务端在收到客户端第三次握手包后,首先要对包进行合法性检查,检查通过后就会检查全连接数是否已经达到最大值,如果没有达到最大值才会开始创建一个新的 tcp_sock 对象表示这个连接并加入到全连接队列。

还是用之前的服务程序来验证下:

模拟测试一

服务器程序,设置 backlog = 128: listen(listenfd, 128);

查看 somaxconn ;

此时,全连接队列 最大值等于 min(somaxconn, backlog) = 128。

使用 wrktcp 工具来测试下:

 查看全连接队列情况,会看到全连接队列一直等于 129。

此外,我们还可以通过命令 netstat -s 来查看 全连接队列溢出 情况 :

 

达到最大值后有什么现象?

当全连接队列达到最大值后,根据内核参数 /proc/sys/net/ipv4/tcp_abort_on_overflow 的不同配置,服务器有相应的处理:

  • 0 :扔掉 客户端 发过来的 ACK 包 ;
  • 1 :发送一个 RST 包给客户端,表示中断这个连接;

linux 默认设置为 0,因为这样可以提高连接建立成功率,有两个原因:

第一,虽然服务端扔掉了这个 ACK 包,但是客户端认为这个连接已经建立成功,并可以发送数据,而数据包里携带的 ACK 也能使服务端成功建立连接。

第二,在服务器扔掉客户端的 ACK 包后,如果客户端一直没有发送数据,服务端这边会触发重试机制,重新向客户端发送第二次握手包 SYN+ACK,客户端收到报文后也会重新发送 ACK 报文,进而提高成功建立连接的机会。

 

 

 

 

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TCP (Transmission Control Protocol) 连接全连接是建立两个进程间通信的两种状态,它们的主要区别在于网络连接的状态和数据传输的过程: **连接(三次握手):** - 在客户端发起连接请求之前,首先发送一个SYN(同步序号)包给服务器。 - 服务器接收到SYN包后,会回应一个SYN+ACK(同步确认)包,同时设置期望序列号。 - 客户端再次回应一个ACK(确认)包,表示收到了服务器的响应并且同意连接条件。 在这个阶段,双方已经交换了必要的控制信息,但是还没有正式的数据传输,因此还不是一个完全的连接状态。 **全连接(四次挥手):** - 当服务器和客户端都准备好开始数据传输时,他们各自维护一个完整的连接(即双方都有SYN+ACK包来回交互)。 - 数据传输结束后,一方希望关闭连接,通常由发起方发送FIN(结束)包,请求释放连接。 - 对方收到FIN包后,发送ACK作为应答,并在完成数据发送后也发送FIN包结束本方向的连接。 - 最后,双方互相发送ACK确认对方已接收FIN,完成断开连接的所有步骤。 总的来说,连接是在连接建立前的试探阶段,而全连接则是实际数据传输的稳定状态。连接节省了资源,因为它不需要立即创建一个全双工连接,但在数据传输开始前需要更多交互。全连接更高效,一旦建立就可以直接传输数据,直到双方协商关闭。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值