文章目录
- 一、什么是 TCP 半连接队列和全连接队列
- 二、TCP 全连接队列
- 1、如何查看进程的 TCP 全连接队列大小?
- 注意
- 2、TCP 全连接队列溢出问题
- 注意
- 3、TCP 全连接队列最大长度
- 三、TCP 半连接队列
- 1、TCP 半连接队列溢出问题
- 2、TCP 半连接队列最大长度
- 3、引申问题
一、什么是 TCP 半连接队列和全连接队列
TCP 三次握手期间,Linux 内核会维护两个队列,分别是:
- 半连接队列,也称 SYN 队列
- 全连接队列,也称 Accept 队列
不管是半连接队列还是全连接队列,都有最大长度限制
二、TCP 全连接队列
1、如何查看进程的 TCP 全连接队列大小?
# -l:显示正在监听(listening)的 socket
# -n:不解析服务名称
# -t:只显示 tcp socket
$ ss -lnt | grep 55535
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 5 192.168.5.28:55535 *:*
注意
ss 命令的 Recv-Q/Send-Q 在「LISTEN 状态」和「非 LISTEN 状态」所表达的含义是不同的
/* Linux Kernel 2.6.32 tcp_diag.c */
static void tcp_diag_get_info( ... )
{.../* LISTEN 状态 */if (sk->sk_state == TCP_LISTEN) {/* 当前全连接队列大小 */ r->idiag_rqueue = sk->sk_ack_backlog;/* 当前全连接队列最大长度 */r->idiag_wqueue = sk->sk_max_ack_backlog;} else {/* 已收到但未被进程读取的字节数 */r->idiag_rqueue = tp->rcv_nxt - tp->copied_seq;/* 已发送但未收到确认的字节数 */r->idiag_wqueue = tp->write_seq - tp->snd_una;}...
}
「LISTEN 状态」的 Recv-Q/Send-Q 含义如下:
- Recv-Q:当前全连接队列大小
- Send-Q:当前全连接队列最大长度
2、TCP 全连接队列溢出问题
Linux 有个参数(tcp_abort_on_overflow),可以指定在 TCP 全连接队列满了的情况下使用什么策略回应客户端
/proc/sys/net/ipv4/tcp_abort_on_overflow 共有两个值:
- 0:全连接队列满了的情况下,服务端丢弃客户端发来的 ACK 报文
- 1:全连接队列满了的情况下,服务端回给客户端 RST 报文
注意
通常情况下,应该把参数 tcp_abort_on_overflow 设为 0,这样有利于应对突发流量
例如,TCP 全连接队列满了的情况下,服务端丢弃客户端发来的 ACK 报文,但此时客户端的连接状态却是 ESTABLISHED,客户端可以在建立好的 TCP 连接上发出请求,只要服务端没有对请求回复 ACK 应答,那么请求就会被客户端重发,如果服务端只是因为短暂的繁忙致使全连接队列满了,那么当全连接队列有空位时,再次接收到的请求报文中由于含有 ACK 标志,仍会触发服务端建立 TCP 连接,所以,tcp_abort_on_overflow=0 可以提高连接建立的成功率
3、TCP 全连接队列最大长度
min(somaxconn,backlog),其中,somaxconn 是 Linux 内核参数,可以通过 /proc/sys/net/core/somaxconn 调整,而 backlog 是 listen() 函数参数
/* Linux Kernel 2.6.32 socket.c */
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{.../* /proc/sys/net/core/somaxconn */somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;/* TCP 全连接队列最大长度 */if ((unsigned)backlog > somaxconn)backlog = somaxconn;...
}
三、TCP 半连接队列
1、TCP 半连接队列溢出问题
- 如果半连接队列满了,并且没有开启 tcp_syncookies,丢弃
- 如果全连接队列满了,并且没有重传 SYN+ACK 的连接请求多于 1 个,丢弃
- 如果没有开启 tcp_syncookies,并且 tcp_max_syn_backlog - 当前半连接队列长度 < (tcp_max_syn_backlog >> 2),丢弃
/* Linux Kernel 2.6.32 tcp_ipv4.c* TCP 第一次握手的 Linux 内核代码 */
int tcp_v4_conn_request( ... )
{.../* 条件 1* 如果半连接队列满了*/if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIESif (sysctl_tcp_syncookies) {want_cookie = 1;} else
#endif/* 如果半连接队列满了,并且没有开启 tcp_syncookies,丢弃 */goto drop;}/* 条件 2* 如果全连接队列满了,并且没有重传 SYN+ACK 的连接请求多于 1 个,丢弃*/if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)goto drop;...if (want_cookie) {...} else if (!isn) {...if ( ... ) {...}/* 条件 3* 如果没有开启 tcp_syncookies,* 并且 tcp_max_syn_backlog - 当前半连接队列长度 < (tcp_max_syn_backlog >> 2),* 丢弃*/else if (!sysctl_tcp_syncookies && sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <(sysctl_max_syn_backlog >> 2)) && ...) {...goto drop_and_release;}...}...
}
2、TCP 半连接队列最大长度
tcp_max_syn_backlog < min(somaxconn,backlog) ? tcp_max_syn_backlog * 2 : min(somaxconn,backlog) * 2,其中,tcp_max_syn_backlog 是 Linux 内核参数,可以通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 调整
3、引申问题
半连接队列最大长度并不代表服务端处于 SYN_RCVD 状态的最大个数
我们在上述内容中总结过 TCP 第一次握手报文被丢弃的三种条件:
- 如果半连接队列满了,并且没有开启 tcp_syncookies,丢弃
- 如果全连接队列满了,并且没有重传 SYN+ACK 的连接请求多于 1 个,丢弃
- 如果没有开启 tcp_syncookies,并且 tcp_max_syn_backlog - 当前半连接队列长度 < (tcp_max_syn_backlog >> 2),丢弃
假设条件一不成立,也就是当前半连接队列长度没有超,而后的条件二也不成立,一旦满足条件三,”半连接队列最大长度并不代表服务端处于 SYN_RCVD 状态的最大个数“ 结论正确,例如 somaxconn = 128, backlog = 511,tcp_max_syn_backlog = 256,计算出半连接队列最大长度是 256,但是按照假设,一旦当前半连接队列长度 > tcp_max_syn_backlog - (tcp_max_syn_backlog >> 2),也就是 256 - 64 = 192,SYN 报文就会被丢弃,那么此时处于 SYN_RCVD 状态的最大个数是 193,而非 256