TCP 三次握手优化
客户端
客户端优化的手段主要是控制 SYN 报文的重传次数
比如,在公司内网,或者服务之间相互调用,不需要多次重传(网络环境比较好),就可以适当减少 SYN 报文的重传次数,以尽快向应用报告错误
SYN 的重传次数可以通过修改 /proc/sys/net/ipv4/tcp_syn_retries
服务端
服务端可以通过优化:
- SYN + ACK 报文的重传次数:
tcp_synack_retries - 半连接队列的大小:调整
tcp_max_syn_backlog、listen 的 backlog、somaxconn - 启用
syn_cookies防止 SYN 攻击 - 全连接队列的大小:调整 listen 的 backlog、
somaxconn
“绕过”三次握手
这里的「绕过」不是真正绕过三次握手,而是 TCP 的 快启动 机制,在 第一次握手时,携带应用层数据 + cookie,减少请求数
例如,对于 HTTP 而言,通常的形式是这样:
如果启用了快启动,是这样的:
- 第一次握手,携带了 请求 Cookies,即要求 Server 启用快启动机制
- Server 如果支持快启动,会在本地生成一个 cookie,在第二次握手时发送给 Client
- 第三次握手,带上 HTTP 请求,这一步与之前一样
重点来了,下一次客户端第一次握手时,除了 SYN 以外,还会带上 Server 之前发来的 cookie + HTTP GET
Server 除了发送 ACK 以外,还会判断 Cookie 是否有效(过期):
- 如果有效,发送 GET 请求对应的资源给 Client
- 如果无效,忽略 Cookie
可以发现,启用了 快启动机制,可以减少一次 RTT 的消耗
此外,cookie 的值是放在 TCP 首部的 Opinion 中的
那么在 Linux 下,如何启用快启动呢?
修改 /proc/sys/net/ipv4/tcp_fastopn,有三个值可选:
- 0:关闭
- 1:作为 Client 启用
- 2:作为 Server 启用
- 3:都启用
注意:需要通信双方都启用才行
TCP 四次挥手优化
主动方
FIN_WAIT_1 的优化
可以通过 tcp_orphan_retries 调整 FIN 报文的重传次数
tcp_orphan_retries 的默认值为 0,但实际上对应的是重传 8 次
FIN_WAIT_2 的优化
如果调用的是 close 来关闭的 TCP 连接,因为 close 后的套接字,收发能力都不具备(因此也叫做 孤儿连接),不应该在 FIN_WAIT_2 等待过长时间,因此可以通过调整 tcp_fin_timeout 来修改等待的时间
默认值为 2MSL,与 TIME_WAIT 的时间一样
但是,如果是调用的 shutdown 来关闭的 TCP 连接,就不能修改等待时间了,shutdown 关闭的套接字,FIN_WAIT_2 的时间是不受 tcp_fin_timeout 控制的
此外,还可以调整 tcp_max_orphans 来修改 孤儿连接 的最大数,以避免太多的连接处于 FIN_WAIT_2
TIME_WAIT
如果有太多连接处于 TIME_WAIT,会造成资源浪费
可以通过修改 tcp_max_tw_buckets 来修改处于 TIME_WAIT 的连接数上限,超过上限以后,如果还有连接要释放,就会直接释放而不进入 TIME_WAIT
此外,连接发起方 还可以 复用 TIME_WAIT 的连接,需要修改两个参数:
- tcp_tw_reuse:1
- tcp_timestamps:1(为报文引入时间戳)
被动方
被动方,可以优化的点就只有 FIN 报文的重传次数了,还是修改 tcp_orphan_retries
此外,还需要注意及时调用 close 来避免大量连接处于 CLOSE_WAIT 状态
TCP 传输数据的优化
滑动窗口
TCP 首部滑动窗口的大小默认只有 16 位,即 65535 字节(64KB)
对于当今的网络来说,64 KB 太小了,要发送一个包,可能得分很多次,这无疑降低了效率
在 TCP 选项字段定义了窗口扩大因子,用于扩大 TCP 通告窗口,最大可以到 1G
因此,可以根据实际情况动态调整窗口的大小
在 Linux 中可以通过修改 tcp_window_scaling 的值为 1 来打开窗口扩大选项(默认已经打开)
当然,窗口也不能真的有 1G 大(除非网络带宽很大)
窗口大小还要考虑一个重要参数:带宽时延积
例如,带宽为 100M,往返时延 RTT 为 10ms,那么,带宽时延积就为 100 * 0.01 = 1M
窗口的大小不应该超过带宽时延积,否则可能会造成网络负载太高,丢包率上升
缓冲区
仅仅修改窗口的大小还不够,OS 会根据发送(接收)缓冲区的大小动态调整发送(接收)窗口的大小
因此,即使启用了窗口扩大选项,如果缓冲区很小,窗口的大小也会很小
如何修改缓冲区的大小呢?
可以通过修改 tcp_wmem 和 tcp_rmem 来调整发送(接收)缓冲区的大小范围,格式如下:
root@SkyLee:~# cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4194304
- 4096:下限为 4096 字节,即使内存有压力的情况下也要保证的
- 16384:初识值为 16384 字节
- 4194304:上限为 4194304 字节
前面提到,窗口的大小不应该超过带宽时延积,因此,这里就要注意设置 缓冲区上限 <= 带宽时延积
在 Linux 中,默认 已经启用了动态修改 发送缓冲区 的大小,但接收缓冲区的调节功能需要修改 tcp_moderate_rcvbuf 为 1 来开启
接收缓冲区的调整是基于 tcp_mem 的:
root@SkyLee:~# cat /proc/sys/net/ipv4/tcp_mem
18603 24804 37206
上面的数字不是字节数,而是页面数(一般来说,1 页 = 4K)
当 TCP 使用内存:
- 小于 18603 * 4KB 时,不需要调整接收缓冲区
- 18603 * 4KB <= used <= 24804 * 4KB 时,开始调整接收缓冲区
- 大于 37206 * 4KB 时,不允许新的 TCP 连接(TCP 可用内存为 0)
注意: 如果要启用自动调整功能,不要在设置 socket 的 SO_SNDBUF 或者 SO_RCVBUF 选项,设置后会关闭自动调整功能
对于 Server 来说,应该提高 TCP 内存的上限,如果内存紧张,可以降低 buffer 的下限,以允许更多的连接数,提高并发量