三次握手在一个HTTP请求中的平均时间占比在10%以上,当网不好,高并发 ,遭遇SYN泛洪攻击的时候,性能就会受影响。
TCP协议是通过OS实现的,调整TCP必须通过OS提供的接口和工具。
客户端的性能优化比服务器简单一些,因为服务器需要在监听端口上被动等待连接,还要保存许多握手的中间状态。
3次握手建立连接的首要目的是同步序列号
。因为只有同步了序列号才可以有可靠的传输,TCP协议中的流量控制和消息丢失后的重发等特性都是依赖序列号实现的。所以三次握手中的报文被称为SYN
的原因——Synchronize Sequence Numbers
。
OS实现了三次握手,但是我们看到的是连接状态
,可以来看下3种状态的意义——
客户端发送SYN
开启了三次握手,这个时候在客户端使用netstat
命令可以看到连接的状态是SYN_SENT
。
通常,服务器会在几ms内返回ACK
,但是如果客户端一直都没有收到ACK
的话,客户端会重发 SYN
,重试的次数是tcp_syn_retries
参数来控制。
一般终止3次握手,最长时间超过了2分钟。
可以降低重试次数,从而可以尽快地把错误暴露给应用程序。
服务器收到SYN
报文后,会返回给客户端SYN+ACK
报文,确认客户端的序列号,同时发送自己的序列号。
这个时候,服务器端出现了新的连接状态,SYN_RCV
。
netstat -s
命令可以得到由于队列已满而引发的失败次数
。队列溢出,会导致SYN被丢弃
。如果SYN被丢弃的个数一直变多,就应该增大SYN半连接队列
(设置Linux的tcp_max_syn_backlog
参数)
当SYN半连接队列满的时候,也可以不丢弃队列,而是开启syncookies
功能就可以在不使用SYN队列的情况下成功建立连接。使用syncookies
建立的连接会导致许多TCP特性均无法使用,所以只有当队列要溢出的时候启动syncookies
。
SYN泛洪攻击:攻击者使用大量的SYN报文发送给服务器,从而导致SYN半连接队列溢出,从而导致客户端的连接无法建立。
客户端收到服务器回复的SYN+ACK
报文后,就会回复ACK
给服务器,同时客户端的连接状态从SYN_SENT
会变成ESTABLISHED
,表明连接成功
。
但是服务器端连接成功的建立要晚一些,当收到客户端发送的ACK
后服务器的连接状态才会变成ESTABLISHED
。
服务器收到ACK连接建立成功后,OS的内核会把连接(这个连接是什么?)从 SYN半连接队列中移出,再移入到accept队列
,等到进程调用accept()
函数的时候把连接
取出来。
TFO:客户端可以在首个SYN报文
中就携带请求,从而节省了1个RTT
的时间。
说了半天,如何优化呢(面试时候怎么答)?
- 当客户端发送SYN的时候,
控制重发次数
。(如果客户端一直没收到服务器回复的ACK,客户端就会重发)。 - 调整
服务器的SYN半连接队列
的长度。(如果SYN半连接队列溢出,SYN报文就会丢失,导致连接建立失败
) - 当网络稳定的时候,减少服务器重发
SYN+ACK报文的次数
。(如果服务器没有收到客户端发送的ACK(ACK报文?
),就会一直重发SYN+ACK报文) - 当SYN队列满了后,开启
syncookie
功能,应对泛洪攻击。(保证连接成功建立) - 进程想取出连接是通过调用
accept()
函数。 - 如果
accept()
队列溢出,系统默认会丢弃ACK,也可以用RST通知客户端连接建立失败。 - 当通过
netstat
发现大量的ACK被丢弃后,可以通过listen()
函数提高accept队列
的上限。 - TFO可以让
HTTP请求
减少一个RTT
的时间。
实际情况: