1. 半连接队列、全连接队列基本概念
- 三次握手中,在第一步server收到client的syn后,把相关信息放到半连接队列中,同时回复syn+ack给client(第二步),同时开启一个定时器,如果超时还未收到 ACK 会进行 SYN+ACK 的重传,重传的次数由 tcp_synack_retries 值确定。在 CentOS 上这个值等于 5。;
- 第三步的时候server收到client的ack,如果这时全连接队列没满,那么从半连接队列拿出相关信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行。
1.1 tcp_abort_on_overflow
- tcp_abort_on_overflow 为 0 表示三次握手最后一步全连接队列满以后 server 会丢掉 client 发过来的 ACK,服务端随后会进行重传 SYN+ACK。
- tcp_abort_on_overflow 为 1 表示全连接队列满以后服务端直接发送 RST 给客户端。
1.2 ss 命令
ss 命令可以查看全连接队列的大小和当前等待 accept 的连接个数,执行 ss -lnt 即可
ss -lnt | grep :9090
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 51 50 *:9090 *:*
Recv-Q 表示 accept 队列排队的连接个数,Send-Q 表示全连接队列(也就是 accept 队列)的总大小。
2. 半连接队列
2.1 半连接队列的长度
半连接队列的大小由3个值决定:
- 用户层 listen 传入的backlog
- 系统变量 net.ipv4.tcp_max_syn_backlog,默认值为 128
- 系统变量 net.core.somaxconn,默认值为 128
2.2 半连接队列长度的计算过程
-
如果用户传入的 backlog 值大于系统变量 net.core.somaxconn 的值,用户设置的 backlog 不会生效,使用系统变量值,默认为 128。
-
将上一步计算的backlog值穿给nr_table_entries,sysctl_max_syn_backlog 为 net.ipv4.tcp_max_syn_backlog 的值
int reqsk_queue_alloc(struct request_sock_queue *queue,unsigned int nr_table_entries)
{nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);nr_table_entries = max_t(u32, nr_table_entries, 8);nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);for (lopt->max_qlen_log = 3;(1 << lopt->max_qlen_log) < nr_table_entries;lopt->max_qlen_log++);
}
- 在 nr_table_entries 与 sysctl_max_syn_backlog 两者中的较小值,赋值给 nr_table_entries(因为sysctl_max_syn_backlog默认是128,如果没有修改,产生的最大值就只能是128)
- 在 nr_table_entries 和 8 取较大值,赋值给 nr_table_entries(最小只能是8)
- nr_table_entries + 1 向上取求最接近的最大 2 的指数次幂(保证是2的幂次)
- 通过 for 循环找不大于 nr_table_entries 最接近的 2 的对数值
3.全连接队列
「全连接队列」包含了服务端所有完成了三次握手,但是还未被应用调用 accept 取走的连接队列。此时的 socket 处于 ESTABLISHED 状态。每次应用调用 accept() 函数会移除队列头的连接。如果队列为空,accept() 通常会阻塞。全连接队列也被称为 Accept 队列。
3.1 全连接队列的长度
全连接队列的大小由3个值决定:
- 用户层 listen 传入的backlog
- 系统变量 net.core.somaxconn,默认值为 128
3.2 全连接队列长度的计算过程
全连接队列的大小是 listen 传入的 backlog 和 somaxconn 中的较小值。
3.3 全连接队列溢出的情况
-
如上图,150166号包是三次握手中的第三步client发送ack给server,然后150167号包中client发送了一个长度为816的包给server。
-
因为在这个时候client认为连接建立成功(因为已经完成三次握手了),但是server上这个连接实际没有ready(因为全连接队列已经满了),所以server直接丢掉client发来的ACK包,并且一段时间后,server认为自己第二次握手的syn+ack包丢包了,因此就重发syn+ack包(SYN+ACK重传的次数是由操作系统的一个文件决定的/proc/sys/net/ipv4/tcp_synack_retries)
-
一段时间后client又收到了syn+ack包,认为第三次握手的ack丢包了,然后重传这816个字节的ack包。一直到超时,client主动发fin包断开该连接。