全连接队列和半连接队列
这可能是全网讲的最细致的全连接和半连接的解析了。
大致释义
首先学习一张图,这是一个三次握手的一个过程。
我在网上找了很久,关于半连接队列(sync queue)和全连接队列(accept queue)是在什么上的一个队列,是操作系统上的?还是应用程序上的?还是每个网络连接的?这个问题困惑我很久,终于在一篇文章找到了。
IP Sysctl — Linux 内核文档 (kernel.org)
我这翻译了下网页,大致讲的就是是在 listen 状态下的 socket 上的连接队列(其实就是 socket 上的,因为 socket 只能绑定一个端口。然后在让绑定的端口进入 listen 状态)
大致的意思为建立连接时,服务端收到客户端发过来建立连接的 syn 包后,那么这个连接就会进入服务端的半连接队列里面,然后服务端给客户端回 syn+ack 包,然后服务端接到客户端回的 ack 包后,连接就会进入服务端的全连接队列中。
基本概念
全连接队列大小取决于 backlog 和 somaxconn 的最小值,也就是 min(backlog,somaxconn)
- somaxconn 是 Linux 内核参数,默认 128,可通过/proc/sys/net/core/somaxconn 进行配置
- backlog 是 listen(int sockfd,int backlog) 函数中的参数 backlog,Tomcat 默认 100,Nginx 默认 511.
半连接队列的大小只能通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 来设置
/proc/sys/net/ipv4/tcp_abort_on_overflow
全连接队列已满就会根据 tcp_abort_on_overflow 策略进行处理。
- 当 tcp_abort_on_overflow=0,全连接队列满了,客户端发来 ACK,服务端直接丢弃该 ACK,此时服务端处于【SYN-RECV】的状态,客户端处于【ESTABLISHED】的状态。在该状态下会有一个定时器重传服务端 SYN/ACK 给客户端(不超过 /proc/sys/net/ipv4/tcp_synack_retries 指定的次数,Linux 下默认 5,目的为了让客户端重发 ACK,当全连接队列有空余了,这样连接还会重新建立起来)。超过后,服务器不在重传,后续也不会有任何动作。如果此时客户端发送数据过来,服务端会返回 RST。(这也就是我们的异常原因了)
- 当 tcp_abort_on_overflow=1,全连接队列满了,客户端发来 ACK,服务端直接返回 RST 通知客户端,表示废掉这个握手过程和这个连接,客户端会报 connection reset by peer。
/proc/sys/net/ipv4/tcp_syncookies
如果 SYN 半连接队列已满,正常默认只能丢弃后续客户端发来的 SYN 包,不过可以通过上面的参数,绕过半连接队列。
开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接 ,当开启了 syncookies 功能就不会丢弃连接。
syncookies 是这么做的:服务器根据当前状态计算出一个值,放在己方发出的 SYN+ACK 报文中发出,当客户端返回 ACK 报文时,取出该值验证,如果合法,就认为连接建立成功,如上图所示
tcp_syncookies 参数主要有以下三个值:
- 0 值,表示关闭该功能;
- 1 值,表示仅当 SYN 半连接队列放不下时,再启用它;
- 2 值,表示无条件开启功能;
tcp_syncookies = 1 时,半连接队列满后,后续的请求就不会存放到半连接队列了,而是在第二次握手的时候,服务端会计算一个 cookie 值,放入到 SYN +ACK 包中的序列号发给客户端,客户端收到后并回 ack ,服务端就会校验连接是否合法,合法就直接把连接放入到全连接队列。
命令学习
查看全连接状态:
查看当前 socket 全连接大小: netstat -tan | grep LISTEN | grep 端口号 中的 Recv-Q
或者 ss -tan | grep LISTEN | grep 端口号 中的 Recv-Q
查看当前 socket 最大全连接大小: netstat -tan | grep LISTEN | grep 端口号 中的 Send-Q
或者 ss -tan | grep LISTEN | grep 端口号 中的 Send-Q
ss
和 netstat
,通过这两个命令可以查看全队列大小。
网上说( ss -tan
结果和 netstat -tan
的结果中的 Recv-Q Send-Q
容易混淆),那是因为 netstat 的老版本的 man 解释和新版本 man 解释不同,截图如下。在新版本中这个释义和 ss
命令的释义是相同的。所以不存在混淆,就是一样的
新版本:
老版本:
(吐槽一下,netstat man 手册老版本为 2000 年版本,新版本为 2014。至少 netstat 在命令中还解释了一下 Recv-Q 和 Send-Q,而 ss 的 man 手册中什么都没写,不知道网友都从哪里找的资料,网上随便一搜,网文说的基本就是和新版本 netstat 释义一样)
贴一下最新的 netstat 和 ss 的在线 man 手册:
国语版本的:
netstat
国语翻译的就是老版本的 新标签页 (debian.org)
ss
命令没找到国语的,贴一个解释比较全的: ss 命令,Linux ss 命令详解:比 netstat 好用的 socket 统计信息,iproute2 包附带的另一个工具,允许你查询 socket 的有关统计信息 - Linux 命令搜索引擎 (wangchujiang.com)
ss
和 netstat
命令获取的 Recv-Q/Send-Q
在「LISTEN 状态」和「ESTABLISHED 状态(同 CLOSE_WAIT,FIN_WAIT1,FIN_WAIT2,TIME_WAIT。这里自己做过实验,确实是相同的,可能这些状态是 ESTABLISHED 之后的状态,所以也认为是 ESTABLISHED 状态)」所表达的含义是不同的
在「LISTEN 状态」时
- Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端
accept()
的 TCP 连接; - Send-Q:当前 socket 全连接最大队列长度。
在「ESTABLISHED 状态(同 CLOSE_WAIT,FIN_WAIT1,FIN_WAIT2,TIME_WAIT)」时
- Recv-Q:已收到但未被应用进程读取的字节数;
- Send-Q:已发送但未收到确认的字节数;
查看半连接状态:
当前半连接多少: ss -tan | grep SYN_RECV | wc -l
查看半连接队列最大长度: cat /proc/sys/net/ipv4/tcp_max_syn_backlog
当前系统参数:
[root@VM-0-16-centos pycharm]# cat /proc/sys/net/ipv4/tcp_syncookies #半连接放不下是,启用 cookies 来建立连接
1
[root@VM-0-16-centos pycharm]# cat /proc/sys/net/ipv4/tcp_abort_on_overflow #全连接满了后,服务端丢弃客户端发来的 ACK 包
0
[root@VM-0-16-centos pycharm]# cat /proc/sys/net/ipv4/tcp_max_syn_backlog #半连接大小
256
[root@VM-0-16-centos pycharm]# cat /proc/sys/net/core/somaxconn #全连接大小
128
为了更加深刻的体会一下,我写段 Python 代码模拟一下全连接堆积的情况。
服务端代码:
import socket
import time
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建 socket 对象
socket.bind(('127.0.0.1', 4323)) # 绑定地址
socket.listen(5) # 全连接队列设置为 5
time.sleep(1000) # 模拟 让服务端在 accept() 之前 hung 住
conn, addr = socket.accept() # 等待客户端连接
print('欢迎 addr{}'.format(addr)) # 打印访问的用户信息
print('欢迎 conn{}'.format(conn))
data = conn.recv(1024)
dt = data.decode('utf-8') # 接收一个 1024 字节的数据
print('收到:', dt)
客户端代码:
import socket
import time
socket = socket.socket() # 创建 socket 对象
socket.connect(('127.0.0.1', 4323)) # 建立连接
ab = '客户端发出'
socket.send(ab.encode('utf-8')) # 发送数据
time.sleep(1000) #为了维持连接,让客户端不退出。
socket.close()
服务端启动:
LISTEN 状态的连接 Send-Q,可以看到全连接大小为 5
[root@VM-0-16-centos pycharm]# python3 server.py &
[1] 28870
[root@VM-0-16-centos pycharm]# ss -lnp | grep 4323
tcp LISTEN 0 5 127.0.0.1:4323 *:* users:(("python3",pid=28870,fd=3))
[root@VM-0-16-centos pycharm]# ss -tan | grep 4323
LISTEN 0 5 127.0.0.1:4323 *:*
客户端启动 3 次:
[root@VM-0-16-centos pycharm]# python3 client.py &
[1] 29030
[root@VM-0-16-centos pycharm]# python3 client.py &
[2] 29038
[root@VM-0-16-centos pycharm]# python3 client.py &
[3] 29046
服务端显示:
这是从 ss -lnp
上已经看到,LISTEN 状态的连接 Recv-Q
是 3, Send-Q
是 5。当前全连接个数为 3。 ss -tan
在 ESTAB 的状态下,可以看到 15,那是因为客户端发了 5 个字给服务端,一个字占用 3 个字节,所以为 15。
[root@VM-0-16-centos pycharm]# ss -lnp | grep 4323
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp LISTEN 3 5 127.0.0.1:4323 *:* users:(("python3",pid=28870,fd=3))
[root@VM-0-16-centos pycharm]# ss -tan | grep 4323
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 3 5 127.0.0.1:4323 *:*
ESTAB 0 0 127.0.0.1:40448 127.0.0.1:4323
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40448
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40452
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40462
ESTAB 0 0 127.0.0.1:40462 127.0.0.1:4323
ESTAB 0 0 127.0.0.1:40452 127.0.0.1:4323
客户端再执行 7 次:
[root@VM-0-16-centos pycharm]# python3 client.py &
[4] 29407
[root@VM-0-16-centos pycharm]# python3 client.py &
[5] 29410
[root@VM-0-16-centos pycharm]# python3 client.py &
[6] 29412
[root@VM-0-16-centos pycharm]# python3 client.py &
[7] 29418
[root@VM-0-16-centos pycharm]# python3 client.py &
[8] 29419
[root@VM-0-16-centos pycharm]# python3 client.py &
[9] 29420
[root@VM-0-16-centos pycharm]# python3 client.py &
[10] 29421
服务端显示:
LISTEN 状态的连接 Recv-Q
是 6, Send-Q
是 5。当前全连接个数为 6。
当全连接满了,后续客户端发送的 ACK 包就丢了,所以会出现四个 SYN-RECV 状态的连接。
解释下为什么会是四个,因为 tcp_abort_on_overflow 参数,当当前全连接大小>设置的全连接大小时,后续的客户端的 ACK 包才会丢弃,5=5 时不触发,6>5 时才触发。
SYN-RECV 状态的连接会进入半连接队列。半连接状态也会满的时候。所以系统上的 SYN-RECV 不能太多。
ss -tan
在 ESTAB 的状态下,Recv-Q 可以看到 15,那是因为客户端发了 5 个字给服务端,一个字占用 3 个字节,所以为 15。 在 ESTAB 的状态下,Send-Q 可以看到 15,那是因为有四个服务端连接状态为 SYN-RECV,而客户端为 ESTAB,代表客户端已经给服务端发送了 5 个字,但是服务端还没有接收。证明在 netstat 中对 ESTAB 中接收的意思和 socket.accept() 不是一个含义。
[root@VM-0-16-centos pycharm]# ss -tan | grep 4323
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 6 5 127.0.0.1:4323 *:*
SYN-RECV 0 0 127.0.0.1:4323 127.0.0.1:40626
SYN-RECV 0 0 127.0.0.1:4323 127.0.0.1:40628
SYN-RECV 0 0 127.0.0.1:4323 127.0.0.1:40632
SYN-RECV 0 0 127.0.0.1:4323 127.0.0.1:40630
ESTAB 0 0 127.0.0.1:40620 127.0.0.1:4323
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40620
ESTAB 0 0 127.0.0.1:40622 127.0.0.1:4323
ESTAB 0 15 127.0.0.1:40628 127.0.0.1:4323
ESTAB 0 0 127.0.0.1:40448 127.0.0.1:4323
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40448
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40622
ESTAB 0 15 127.0.0.1:40632 127.0.0.1:4323
ESTAB 0 15 127.0.0.1:40626 127.0.0.1:4323
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40452
ESTAB 0 0 127.0.0.1:40618 127.0.0.1:4323
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40618
ESTAB 15 0 127.0.0.1:4323 127.0.0.1:40462
ESTAB 0 0 127.0.0.1:40462 127.0.0.1:4323
ESTAB 0 0 127.0.0.1:40452 127.0.0.1:4323
ESTAB 0 15 127.0.0.1:40630 127.0.0.1:4323
[root@VM-0-16-centos pycharm]# ss -lnp | grep 4323
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp LISTEN 6 5 127.0.0.1:4323 *:* users:(("python3",pid=28870,fd=3))
未完待续。。。
后面我会模拟下其他情况。。。
相关文章推荐:
内容跟我文章讲相驳时,以我的为准
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: 排查网页打不开问题
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论