文章目录
- 1 三次握手
- 2 参数优化
- 2.1 tcp_syn_retries--->SYN重传次数
- 2.2 tcp_synack_retries--->ACK重传次数
- 2.3 tcp_retries2--->发送数据失败重传次数
- 2.4 TCP keepalive--->保活机制
- 2.5 tcp_max_syn_backlog/somaxconn--->半/全连接队列长度
- 2.6 tcp_syncookies--->cookie机制
- 2.7 tcp_abort_on_overflow--->RST复位
- 2.8 tcp_fastopen--->绕过三次握手
- 3 总结
参考资料:
1. 字节一面:服务端挂了,客户端的 TCP 连接还在吗?
在客户端和服务端建立连接时候,会经过3次交互,才能正式建立一个连接。这篇大致介绍下三次握手的交互过程以及如何优化。
1 三次握手
- 第一次交互:Client会发送一个SYN包给到Server,SYN包中包含了Client的一个序列号J,此时Client的状态为SYN_SENT(请求连接状态);
- 第二次交互:Server收到SYN包后,会返回一个ACK包,用于通知Client收到了SYN包,然后也会给Client发送一个SYN包,用于通知Client自己的序列号为K,此时Server的状态为SYN_RCVD(表示Server已经收到Client的SYN报文);
- 第三次交互:Client收到Server的ACK+SYN之后,会给Server再次发送一个ACK包,表示已经收到SYN包了,此时Client处于ESTABLISHED状态,Server收到Client的ACK后,也会处于ESTABLISHED状态。
2 参数优化
2.1 tcp_syn_retries—>SYN重传次数
在上面第一次交互时候,Client发送SYN包之后的状态为SYN_SENT,那么此时如果服务器没有收到,即服务端没有返回ACK+SYN报文,会发生什么情况?
如果Client没有收到Server的ACK响应,那么就会多次重传,如果多次重传后,仍然没有收到ACK响应,那么就会返回time out异常。
如下指令查看重传次数:
# cat /proc/sys/net/ipv4/tcp_syn_retries
6
重传时的超时时间为,1,2,4,8,16,32,64,逐渐递增,即第一次发送SYN包后,超时1s;进入第1次重传,超时2s;进入第2次重传,超时4s;进入第3次重传,超时8s;进入第4次重传,超时16s;进入第5次重传,超时32s;进入第6次重传,超时64s,上报异常。
因此在某些场景下,如局域网通信,我们可以修改重传次数来减少等待时间,让应用层更快进行处理异常。
2.2 tcp_synack_retries—>ACK重传次数
第二次交互时候,如果Server发送完SYN+ACK,没有收到Client的ACK,又会发生什么情况?
如果没有收到Client的ACK,Server端也会进行重传,重传次数由tcp_synack_retries参数决定。
# cat /proc/sys/net/ipv4/tcp_synack_retries
5
重传时的超时时间为,1,2,4,8,16,32,逐渐递增。
因此在某些场景下,如网络繁忙不稳定,我们可以增大重传次数,减少链路干扰导致的异常。
2.3 tcp_retries2—>发送数据失败重传次数
第三次交互,Client发送完ACK包之后,就处于ESTABLISHED状态,此时如果Server没有收到ACK,就会重传,根据上面的tcp_synack_retries参数,重传5次后,Server断开连接,此时Client端仍然处于ESTABLISHED状态,如果此时Client发送数据,又会发生什么情况?
这种情况下,Server已经没有连接了,肯定是收不到数据的,但是Client不知道,仍然会发送,然后超时,继续发送,超时,重传次数由tcp_retries2 决定。
# cat /proc/sys/net/ipv4/tcp_retries2
15
超时时间受内核中TCP_RTO_MAX、TCP_RTO_MIN相关参数限制,15次大约是15分钟,参考链接1:
2.4 TCP keepalive—>保活机制
如果Client一直不发送数据,那么什么时候才知道Server已经断开了TCP连接?
如果Client没有开启TCP keepalive 机制,那么这种情况下Client就会一直保持TCP连接;如果开启TCP keepalive 机制,那么Client就会定时发送探测报文,根据Server是否回复来得知Server是否断开TCP连接。
相关参数如下:
# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
tcp_keepalive_time为7200s,也就是如果没有任何数据交互,那么就会在7200s之后启动保活,开始发送探测报文;
tcp_keepalive_intvl为75s,表示每次发送的探测报文间隔为75s;
tcp_keepalive_probes为9,也就是9次发送探测报文都没有收到,认为Server已经断开连接,Client就会中断本次连接。
7200+75*9=7875,也就是7875s之后才能真正确认Server已经断开连接。
2.5 tcp_max_syn_backlog/somaxconn—>半/全连接队列长度
在第二次交互中,Server处于SYN_RCV状态,此时处于一个半连接状态,Server会将这个半连接状态的信息放到半连接队列中,如果这个半连接队列满了,那么Server就无法建立新的连接了。
我们可以通过如下指令,查看是否出现半连接队列满的情况:
# netstat -s | grep "SYNs to LISTEN"
332566 SYNs to LISTEN sockets dropped
如果间隔几秒执行几次,发现这个累加值一直上升,那么说明出现了半连接队列满的情况。
# cat /proc/sys/net/ipv4/tcp_max_syn_backlog
128
可以通过调整tcp_max_syn_backlog 的值来修改半连接队列的长度,修改时还需要同步修改somaxconn 的值以及backlog来配合使用。
# cat /proc/sys/net/core/somaxconn
128
somaxconn表示的是socket监听队列的最大长度,或者说全连接状态队列的长度。
backlog可使用listen 函数的 backlog 参数进行修改。
如果在某些场景下,比如经常处理新的请求的场景,可以修改半连接队列和全连接队列的长度。
2.6 tcp_syncookies—>cookie机制
如果在半连接队列满的情况下,开启了tcp_syncookies,那么Server不会丢弃这个连接,会根据Client的SYN包计算出一个cookie值,将它放到ACK+SYN报文中发送出去,然后收到Client的ACK后,会进行计算验证,如果合法,那么就认为建立连接成功。
# cat /proc/sys/net/ipv4/tcp_syncookies
1
0 表示关闭该功能;1 表示仅当 SYN 半连接队列放不下时,再启用它;2表示无条件开启功能。
2.7 tcp_abort_on_overflow—>RST复位
在第三次交互中,Client发送ACK之后就处于连接状态,而Server收到ACK后会将连接从半连接队列中删除,然后创建新的连接,将其加到全连接队列中。如果此时全连接队列满了,那么就会根据tcp_abort_on_overflow的值进行判定,如果tcp_abort_on_overflow值为0,则丢弃这个ACK;如果值为1,则会回复一个RST复位报文给Client,告诉Client连接已经建立失败,丢弃连接。
# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0
2.8 tcp_fastopen—>绕过三次握手
在三次握手中,第三次交互时候,即Client发送ACK回复的时候,ACK中是可以携带HTTP请求的,因此整个交互,是至少需要一个RTT时间(即Client到Server,然后Server到Client的往返时间)才能发送HTTP请求。
问题来了,能否节省这一个RTT时间,进行HTTP请求?
Google提出了TCP fast open方案,使Client可以在第一次交互时候Client发送的SYN报文中直接携带请求,这样就可以节省了一个RTT时间。
具体是分为两个步骤进行的:
- 第一个步骤:正常的三次握手机制,只不过在Client发送SYN报文时候,包含了Fast Open选项,选项中的Cookie为空,然后支持Fast Open的Server收到SYN报文后,会生成一个cookie,放进ACK+SYN报文中,然后ACK收到报文,缓存cookie到本地。
- 第二个步骤:如果下次再次向Server建立连接,那么在第一次交互时候,Client发送的SYN报文中包含HTTP请求和第一个步骤中缓存的cookie,Server收到SYN报文后,会对cookie进行验证,如果验证成功,则HTTP请求数据有效,送到对应的处理程序中,如果请求无效则丢弃HTTP请求数据。不管是否有效,都会回复ACK+SYN报文,Client也会再次回复ACK。但是,这种如果有效,那么在第一次交互时候,SYN包中已经携带有效数据了,省了一个RTT时间。
# cat /proc/sys/net/ipv4/tcp_fastopen
1
0:关闭1:作为客户端使用 Fast Open 功能2:作为服务端使用 Fast Open 功能3:无论作为客户端还是服务器,都可以使用 Fast Open 功能。
TCP Fast Open 功能需要客户端和服务端同时支持,才有效果。
3 总结
在本篇中,引申出以下几个内核参数,除了tcp_retries2和tcp_keepalive_time外,其它内核参数均是和三次握手相关的。