首先我们根据下图,了解TCP三次握手与TCP队列之间的联系
第一次握手
client发送SYN给server,server收到来自client的SYN后,就把相关信息放入到syns queue
第二次握手
server发送SYN、ACK给client
第三次握手
client收到来自server的SYN、ACK后,发送确认包ACK给server,server收到client的ack,如果这时全连接队列没满,那么从半连接队列拿出相关信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行
# 查看tcp_abort_on_overflow的状态
# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
—— tcp_abort_on_overflow 为0表示过一段时间后重新向client发送ACK+SYN(重复第二次握手)
——tcp_abort_on_overflow 为1表示第三握手时如果全连接队列满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接,
客户端异常中可以看到connection reset by peer的错误
也就是说,client进行connect之后先进入server的半连接队列,client就直接进入established状态,如果server的全连接队列(accept队列)未满,则将其放入全连接队列(未被应用层进行accpet函数调用取走),然后server也进入established状态。如果全连接队列满了,则server停留在syn_recv状态。
队列的类型
socket接收的所有连接都存放在队列类型中,队列有以下两种:
- syns queue(半连接队列,用来保存处于SYN_SENT和SYN_RECV状态的请求)
- accept queue(全连接队列,用来保存处于established状态,但是应用层没有调用accept取走的请求)
队列的长度是由以下参数决定的:
- 全连接队列的最大值取决于:min(backlog, /proc/sys/net/core/somaxconn),在linux内核2.2版本以后,backlog参数控制的accept queue的大小,backlog是在socket创建的时候传入的,属于listen函数里的参数;somaxconn是内核的参数,默认是128
- 半连接队列的最大值取决于:max(/proc/sys/net/ipv4/tcp_max_syn_backlog),CentOS7默认为512,当启用syncookies时,没有逻辑最大长度,忽略tcp_max_syn_backlog设置,syncookies的设置可以防范SYN flood攻击,查看syncookies是否启动:
# cat /proc/sys/net/ipv4/tcp_syncookies
# 如果是“1”说明已启用,为“0”说明未启用
为什么全连接队列由backlog与somaxconn的最小值决定呢?
其实,backlog可以比喻为酒店的大厅,somaxconn可以比喻为大厅里的座位,能服务的人数由大厅里的座位数决定,但大厅里的座位的多少又由大厅的大小而决定,所以服务端能接收的完整连接数上限是由backlog和somaxconn的最小值而决定。listen方法指定的backlog是在用户态指定的,内核态的参数优先级高于用户态参数,所以即使在listen方法里面指定backlog是一个大于somaxconn的值,socket在内核态运行时还会检查一次somaxconn,如果连接数超过somaxconn就会等待。
当全连接队列已满且半连接队列未满的情况下:
当客户端发起一个syn分节时,服务端不会丢弃该syn分节,而是直接响应ack和syn,这时客户端响应ack,并成为established状态,而服务端收到ack响应后,试图将该syn分节从半连接队列中移除,并加入全链接队列,然后由于全连接队列已经满了,这时,在默认情况下,服务端啥也不做,而且不会将该连接由SYN_RECV变成ESTABLISHED,服务端仅仅只是创建一个定时器,以固定间隔重传syn和ack到客户端,直到到达系统默认的synack重传阖值,然后服务端将处于半连接队列里面的syn分节丢弃。
当全连接已满且半连接队列也满的情况下:
当客户端发起一个syn分节时,服务端发现半连接队列已经满了,同时该syn分节尚未重传过,服务端直接丢弃该syn分节,然后客户端过了4秒重传syn分节,这个时候服务端发现半连接队列已满同时,该syn分节已经重传过了,服务端收下了该syn分节,并响应客户端syn+ack,客户端收到syn+ack后,响应ack。客户端三次握手已经完成,而服务端收到ack之后,发现全连接队列是满的,这个时候不会将该连接从SYN_RECV转换成ESTABLISHED,服务端创建定时器,定时重传syn+ack, 直到到达系统默认的synack重传阖值,然后服务端将处于半连接队列里面的syn分节丢弃
TCP连接队列溢出指标
# cat /proc/net/netstat | awk '/TcpExt/ { print $21,$22 }'
ListenOverflows ListenDrops
165 165
Tcp ListenOverflows tcp_v4_syn_recv_sock():三路握手最后一步完全之后,Accept queue队列超过上限时加1
Tcp ListenDrops tcp_v4_syn_recv_sock():任何原因,包括Accept queue超限,创建新连接,继承端口失败等,加1