Linux协议栈accept和syn队列问题
环境:
Client 通过tcp 连接server,server端只是listen,但是不调用accept。通过netstat –ant查看两端的连接情况。
server端listen,不调用accept。
client一直去connect server。
问题:
运行一段时间后,为什么server端的ESTABLISHED连接的个数基本是固定的129个,但是client端的ESTABLISHED连接的个数却在不断增加?
分析
Linux内核协议栈为一个tcp连接管理使用两个队列,一个是半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求),一个是accpetd队列(用来保存处于established状态,但是应用层没有调用accept取走的请求)。
第一个队列的长度是/proc/sys/net/ipv4/tcp_max_syn_backlog,默认是1024。如果开启了syncookies,那么基本上没有限制。
第二个队列的长度是/proc/sys/net/core/somaxconn,默认是128,表示最多有129个established链接等待accept。(为什么是129?详见下面的附录1)。
现在假设acceptd队列已经达到129的情况:
client发送syn到server。client(SYN_SENT),server(SYN_RECV)
server端处理流程:tcp_v4_do_rcv--->tcp_rcv_state_process--->tcp_v4_conn_request
if(sk_acceptq_is_full(sk) inet_csk_reqsk_queue_yong(sk)>1)
goto drop;
inet_csk_reqsk_queue_yong(sk)的含义是请求队列中有多少个握手过程中没有重传过的段。
在第一次的时候,之前的握手过程都没有重传过,所以这个syn包server端会直接drop掉,之后client会重传syn,当inet_csk_reqsk_queue_yong(sk) 1,那么这个syn被server端接受。server会回复synack给client。这样一来两边的状态就变为client(ESTABLISHED), server(SYN_SENT)
Client收到synack后回复ack给server。
server端处理流程: tcp_check_req--->syn_recv_sock-->tcp_v4_syn_recv_sock
if(sk_acceptq_is_full(sk)
goto exit_overflow;
如果server端设置了sysctl_tcp_abort_on_overflow,那么server会发送rst给client,并删除掉这个链接;否则server端只是记录一下LINUX_MIB_LISTENOVERFLOWS(详见附录2),然后返回。默认情况下是不会设置的,server端只是标记连接请求块的acked标志,之后连接建立定时器,会遍历半连接表,重新发送synack,重复上面的过程(具体的函数是inet_csk_reqsk_queue_prune),如果重传次数超过synack重传的阀值(/proc/sys/net/ipv4/tcp_synack_retries),会把该连接从半连接链表中删除。
一次异常问题分析
Nginx通过FASTCGI协议连接cgi程序,出现cgi程序read读取socket内容的时候永远block。通过netstat查看,cgi程序所在的服务器上显示连接存在,但是nginx所在的服务器上显示不存在该连接。
下面是原始数据图:
我们从上面的数据流来分析一下:
出现问题的时候,cgi程序(tcp server端)处理非常慢,导致大量的连接请求放到accept队列,把accept队列阻塞。
148021 nginx(tcp client端) 连接cgi程序,发送syn
此时server端accpet队列已满,并且inet_csk_reqsk_queue_yong(sk) > 1,server端直接丢弃该数据包
148840 client端等待3秒后,重传SYN
此时server端状态与之前送变化,仍然丢弃该数据包
150163 client端又等待6秒后,重传SYN
此时server端accept队列仍然是满的,但是存在了重传握手的连接请求,server端接受连接请求,并发送synack给client端(150164)
150166 client端收到synack,标记本地连接为ESTABLISHED状态,给server端应答ack,connect系统调用完成。
Server收到ack后,尝试将连接放到accept队列,但是因为accept队列已满,所以只是标记连接为acked,并不会将连接移动到accept队列中,也不会为连接分配sendbuf和recvbuf等资源。
150167 client端的应用程序,检测到connect系统调用完成,开始向该连接发送数据。
Server端收到数据包,由于acept队列仍然是满的,所以server端处理也只是标记acked,然后返回。
150225 client端由于没有收到刚才发送数据的ack,所以会重传刚才的数据包
150296 同上
150496 同上
150920 同上
151112 server端连接建立定时器生效,遍历半连接链表,发现刚才acked的连接,重新发送synack给client端。
151113 client端收到synack后,根据ack值,使用SACK算法,只重传最后一个ack内容。
Server端收到数据包,由于accept队列仍然是满的,所以server端处理也只是标记acked,然后返回。
151896 client端等待3秒后,没有收到对应的ack,认为之前的数据包也丢失,所以重传之前的内容数据包。
152579 server端连接建立定时器生效,遍历半连接链表,发现刚才acked的连接,
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)