摘要
三次握手性能提升
三次握手性能提升主要通过以下方面:
SYN报文重传次数的优化
客户端在建立连接时会首先发送SYN报文,但是假设此时你没有收到服务端SYN+ACK的响应报文,客户端此时会重传SYN报文,此时你需要根据实际情况来调整SYN报文的重传次数,以便客户端能够及时得到反馈。
# 查看SYN报文重传次数
cat /proc/sys/net/ipv4/tcp_syn_retries
调整SYN半连接队列的长度
服务端在收到SYN包后,会回复SYN+ACK包,并且把链接放入SYN半连接队列,假设半连接队列增的速度大于取的速度,半连接队列会越来越多,直到无法容纳更多的连接。
此时需要增加半连接队列的大小,增大半连接队列的操作相对还是比较繁琐的:
# 查看tcp_max_syn_backlog的值
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
# 查看somaxconn的值
cat /proc/sys/net/core/somaxconn
除了增大半连接队列,还可以通过开启tcp_syncookies避开使用半连接队列:
# 查看tcp_syncookies的默认值
# 0:不开启该功能
# 1:当半连接队列满时,开启该功能
# 2:无条件开启该功能
cat /proc/sys/net/ipv4/tcp_syncookies
调整SYN+ACK报文的重传次数
服务器发送SYN+ACK报文的次数,假设因为网络波动一直收不到ACK报文响应,此时应该略为调大重发次数。重发次数由内核参数tcp_synack_retries控制。
# 查看SYN+ACK报文的最大重传次数
cat /proc/sys/net/ipv4/tcp_synack_retries
调整accpet全连接队列的长度
服务器在收到第三次握手的ACK报文以后,会初始化一个完全的连接并放入全连接队列中等待应用程序取走,假设应用程序无法及时取走就有可能导致全连接队列被放满并溢出。
全连接队列溢出后的行为我们也可以通过参数进行控制,详情请见TCP连接队列这篇文章。
全连接队列的大小=min(somaxconn, backlog),backlog需要应用程序控制。
# 查看somaxconn的值
cat /proc/sys/net/core/somaxconn
绕过三次握手
三次握手造成的影响就是HTTP请求必须在1个RTT以后才可以发送数据,2个RTT才能接收数据。
在Linux3.7内核之后,提供了TCP Fast Open功能,该功能可以减少TCP连接建立的延时,除首次建立TCP连接后续的连接建立过程中在第一次握手就可以发送数据(也就是0 RTT延时),1个RTT以后就可以接收数据。
TCP Fast Open功能由内核参数tcp_fastopen来控制:
# 查看TCP Fast Open功能开启情况
# 0:关闭
# 1:作为客户端使用Fast Open功能
# 2:作为服务端使用Fast Open功能
# 3:无论作为客户端还是服务端,都使用Fast Open功能
cat /proc/sys/net/ipv4/tcp_fastopen
TCP Fast Open的工作原理

在客户端首次建立连接的过程如下:
之后客户端再向服务器建立连接的过程就发生了变化:
通过上述图和流程可以看出。TCP Fast Open会减少整个数据的RTT延时。
四次挥手性能提升
安全关闭连接必须通过四次挥手,应用程序需要调用close或者shutdown方法发出FIN报文。
TCP四次挥手性能提升主要有以下优化方案:
什么是孤儿连接?
close函数意味着完全断开连接,无法传输数据也不能发送数据,调用了close方法的一方的连接称为孤儿连接。netstat -p会发现连接对应的进程名为空。
shutdown函数的区别
int shutdown(int sock, int howto);
shutdown函数断开连接的方式主要取决于第二个参数:
调整FIN报文的重传次数(主动方)
主动断开方发送FIN报文后,连接处于FIN_WAIT_1状态,但是如果一致收不到被动方的ACK报文,那么连接将会一直处于FIN_WAIT_1状态,并且内核会因为超时重发FIN报文,FIN报文的重发次数由tcp_orphan_retries控制:
# 查看FIN报文的重发次数,默认值是0,表示特指重发8次
cat /proc/sys/net/ipv4/tcp_orphan_retries
如果FIN_WAIT_1状态的连接很多,可以考虑降低tcp_orphan_retries的值,当FIN报文重传次数超过该值时连接会被直接关闭掉。
调整孤儿连接的个数
在遇到恶意攻击,FIN报文无法发出,FIN报文无法发出的原因是:
这种情况下可以通过调整孤儿连接的数量即可,孤儿连接的数量由tcp_max_orphan控制:
# 查看孤儿连接的数量
cat /proc/sys/net/ipv4/tcp_max_orphan
当孤儿连接数量大于上述的值时,新增的孤儿连接不再走四次挥手,直接发送RST报文强制关闭。
调整FIN_WAIT_2的状态持续时间
主动方在收到ACK报文后,会处于FIN_WAIT_2状态,表示主动方发送通道关闭,等待被动方发送FIN报文,关闭被动方的发送通道。
如果连接使用shutdown函数关闭的,连接可以一直处于FIN_WAIT_2状态,因为它可能还可以发送或接收数据。但对于close函数关闭的孤儿连接,由于无法再发送和接收数据,所以这个状态不可以持续太久,这个状态的最大持续时间受内核参数tcp_fin_timeout控制:
# 查看孤儿连接FIN_WAIT_2的时间,默认值是60s
cat /proc/sys/net/ipv4/tcp_fin_timeout
调整TIME_WAIT状态个数上限
当收到被动方发来的FIN报文后,主动方会立刻回复ACK,表示确认对方的发送通道已经关闭,紧接着进入TIME_WAIT状态。
MSL定义了一个报文在网络中的最长生存时间,TIME_WAIT和FIN_WAIT_2都会保持2MSL时长,在Linux中MSL固定为30s,所以TIME_WAIT和FIN_WAIT_2都是60s。
TIME_WAIT的最大个数受内核参数tcp_max_tw_bucktes控制,当TIME_WAIT的数量超过该参数的限制时,连接关闭将不再经历TIME_WAIT而直接关闭。
# 查看TIME_WAIT的最大个数
cat /proc/sys/net/ipv4/tcp_max_tw_buckets
tcp_max_tw_bucktes的值并不是越大越好,因为内存和端口都是有限资源。
复用TIME_WAIT连接
既然tcp_max_tw_bucktes的参数无法无限变大,还有一种方式就是复用TIME_WAIT状态的连接。是否复用TIME_WAIT的是通过内核参数tcp_tw_reuse参数进行控制,该参数只对客户端(调用connect)有效:
# 查看tcp_tw_reuse功能
cat /proc/sys/net/ipv4/tcp_tw_reuse
使用这个选项,还需要双方都打开对TCP时间戳的支持:
# 查看是否打开时间戳功能
cat /proc/sys/net/ipv4/tcp_timestamps
时间戳带来的好处如下:
复用TIME_WAIT只使用于连接发起方,并且需要连接在TIME_WAIT状态的时间超过1s才可以复用。
小心大量的close wait状态连接
CLOSE WAIT状态出现在被动方在收到FIN报文以后并发出ACK回应以后的一种状态,当被动方再次发送FIN报文以后便会进入LAST ACK状态,假如有大量的CLOSE WAIT状态的连接,此时一定小心作为被动方的你的应用程序是不是有BUG,没有调用close函数去发送FIN报文。
数据传输性能提升
数据传输性能提升的主要方法为:
扩充滑动窗口
TCP连接由内核维护,内核会为每个连接建立内存缓冲区。
TCP报文发出去以后,并不会立即从内存中删除,因为重传时还需要使用。
TCP连接在过多时,通过free命令可以看出buff/cache内存是增大的。
流量控制中我们已经讲述了滑动窗口对数据包发送的影响,TCP头部中窗口字段只占用16位(2字节),因此最大可以发送64KB大小的数据,随着网络的高速发展,64KB的窗口其实是很小的,因此在TCP中采用了扩充窗口的方式,具体如下:
在TCP选项字段中定义了窗口扩大因子,其值大小是2^14,这样TCP窗口的位数从16位扩大为30位(2^16 * 2^14 = 2^30),此时窗口最大值可以达到1GB。
是否扩充滑动窗口由内核参数tcp_window_scaling控制:
# 查看是否启用扩容滑动窗口
# 默认是打开
cat /proc/sys/net/ipv4/tcp_window_scaling
使用扩充滑动窗口功能需要在各自的SYN报文中发送这个选项,并且被动方必须在主动方的SYN报文包含这个选项时才可以在自己的SYN报文中发送这个选项。
如何确定网络的最大传输速度?
网络是有带宽限制的,带宽描述的是网络传输能力,它与内核缓冲区的计量单位是不同的:
什么是带宽时延积(BDP)?
带宽时延积决定了飞行报文的大小,飞行报文指的是客户端到服务端上的网络数据包。
带宽时延积BDP = RTT * 带宽
假设带宽是100MB/s,RTT为10ms,那么BDP就为1MB的字节,如果在网络上的报文大小超过了1MB,就会导致网络过载,容易丢包。
发送缓冲区决定了发送窗口的上限,发送窗口又决定了已发送未确认的飞行报文的上限,因此发送缓冲区不能超过带宽时延积。
发送缓冲区的大小最好是往带宽时延积靠近。
如何调整缓冲区大小?
Linux中发送缓冲区和接收缓冲区都可以使用参数动态调节。
发送缓冲区由内核tcp_wmem参数控制:
# 查看发送缓冲区的范围
# 默认(单位字节)是4096 16384 4194304
# 第一个数值动态调节的最小值4KB
# 第二个数值是发送缓冲区的初始默认值86KB
# 第三个数值是动态调节的最大值4MB
cat /proc/sys/net/ipv4/tcp_wmem
发送缓冲区是自动调节,当发送方的数据被确认后并且无新数据要发送,发送缓冲区的内存就会被释放。
接收缓冲区由内核参数tcp_moderate_rcvbuf和tcp_rmem参数共同控制:
# 查看是否启用接收缓冲区自动调节
# 默认值是1,表示开启
cat /proc/sys/net/ipv4/tcp_moderate_rcvbuf
# 查看接收缓冲区的范围
# 默认(单位字节)是4096 131072 6291456
# 第一个数值是动态调节的最小值4KB
# 第二个参数是接收缓冲区的初始默认值128KB
# 第三个参数是动态调节的最大值6MB
cat /proc/sys/net/ipv4/tcp_rmem
接收缓冲区可以根据系统空闲内存来调节接收窗口:
如何确定内存是否紧张
内存是否紧张是由内核参数tcp_mem控制:
# 查看内存范围
# 默认(单位是页,1页=4KB)是10320 13762 20640
# 当TCP内存小于4KB*10320时,不需要进行调节
# 当TCP内存位于第一个和第二个值时,内核开始调节接收缓冲区的大小
# 当TCP内存大于第三个值时,内核不再为TCP分配新内存,新连接无法建立
cat /proc/sys/net/ipv4/tcp_mem
一定不要在你的应用程序的Socket上设置SO_SNDBUF或者SO_RCVBUF,一旦设置会关闭缓冲区的动态调整功能。