作为一个后端程序员,网络连接这块是一个绕不过的砍,当你在做服务器优化的时候,网络优化也是其中一环,那么作为网络连接中最基础的部分 -TCP 连接
你了解吗?今天我们来仔细看看这个部分。
<!– more –>
TCP 建立连接 - 三次握手
详解
- 客户端和服务器还未建立连接,但服务器一般处于
listen
状态 - 客户端主动建立连接,向服务器发送 SYN 报文,客户端变为
SYN_SENT
状态 - 服务器收到客户端发送的报文,也回了一个 SYN 报文,包含了一个 ack。此时,服务器变为
SYN_RCVD
状态 - 客户端收到了服务器发送的 SYN 报文,确认了 ack,它将向服务器发送一个 ACK 报文。此时,客户端变为
ESTABLISHED
- 服务器收到客户端的 ACK 报文,确认了 ack。此时,服务器也变为
ESTABLISHED
- 服务器和客户端可以正常通信了
其中步骤 2~4 就是三次握手,那么为什么需要三次握手呢?为什么不是一次或者两次握手呢?
首先,我们需要知道,只有当服务器和客户端都能确保自己能够发消息和接收消息,这次网络通信才算成功的。
步骤 2 的作用是让服务器知道了自己是可以接收消息的。
步骤 3 的作用是让客户端知道自己发送消息和接收消息的功能是 OK 的,发送消息的能力是通过服务器返回的 ack=x+1
确认的,因为这个值基于当初客户端发送的消息seq=x
。接收消息的能力是因为收到了服务器的返回。
步骤 4 的作用是让服务器端知道自己发送消息的能力是 OK 的(和步骤 3 类似)。
linux 查看
linux 服务器可以利用 netstat -anp | grep tcp
命令,查看服务器上各个端口和应用的连接状态。
你还可以通过修改 linux 的配置文件/etc/sysctl.conf
,调整各个状态的数量
SYN_SENT
状态相关
- 主动建立连接时,发 SYN(步骤 2)的重试次数
nct.ipv4.tcp_syn_rctries = 6
- 建立连接时的本地端口可用范围
net.ipv4.ip_local_port_range = 32768 60999
SYN_RCVD
状态相关
-
SYN_RCVD
状态连接的最大个数
net.ipv4.tcp_max_syn_backlog
- 被动建立连接时,发 SYN/ACK(步骤 3)重试次数
net.ipv4.tcp_synack_retries
说完了 TCP 建立连接,接下来,我们再来看看 TCP 正常断开连接的过程
TCP 断开连接 - 四次挥手
详解
- 客户端与服务器端正常传输数据
- 客户端主动断开连接,向服务器端发送 FIN 报文,客户端变为
FIN_WAIT1
状态 - 服务器收到客户端的 FIN 后,向客户端发送 ACK 报文,服务器变为
CLOSE_WAIT
状态 - 客户端收到服务器的 ACK 报文后,客户端变为
FIN_WAIT2
状态 - 服务器向客户端发送 FIN 报文,服务器变为
LAST_ACK
状态 - 客户端收到服务器发送的 FIN 报文后,向服务器发送 ACK 报文,客户端变为
TIME_WAIT
状态 - 服务器收到客户端的 ACK 报文后,服务器变为
CLOSED
状态 - 客户端经过 2MSL(max segment lifetime,报文最大生存时间)时间后,也变为
CLOSED
状态
其中,步骤 2、3、5、6 即为 4 次挥手。
TIME_WAIT
状态及其优化
看完之后,大家想必会有一个疑问,为什么 TIME_WAIT
状态需要保持 2MSL?因为这可以保证至少一次报文的往返时间内,端口是不可复用的。
假设 TIME_WAIT
状态的持续时间很短,我们来模拟下面这种场景:
- 客户端向服务器端发送了三条报文,其中第 3 条报文卡在网络中,服务器只收到了前两条,向客户单发送 ACK=2,客户端重新发送第三条报文。
- 服务器主动发送 FIN 报文,客户端收到后发送 FIN、ACK,服务器端收到后发送 ACK 并进入
TIME_WAIT
状态(假设这个状态很短)。 - 现在服务器又再次和客户端建立连接,三次握手之后开始发送正常数据,结果之前卡住的第三条报文,现在终于发送到服务器,但服务器也不知道该如何处理这条报文。
因此这也是 TIME_WAIT
状态需要保持 2MSL 的原因,如果这么长时间也没有收到报文,即使有正确的报文从客户端发出,也已经过期了,因此不会影响到之后的通信。
但这同样也会带来一个问题,TIME_WAIT
状态保持的时间较长,假设服务器端有大量 TIME_WAIT
状态的 TCP 连接,就相当于白白浪费掉大量的服务器资源(端口)。此时,我们可以通过修改以下配置进行服务器调优:
net.ipv4.tcp_tw_reuse = 1
- 开启后,作为客户端时新连接可以使用仍然处于
TIME_WAIT
状态的端口 - 由于 timestamp 的存在,操作系统可以拒绝迟到的报文(例如上面说的第三条报文),可以利用以下配置:
net.ipv4.tcp_timestamps = 1
其他状态的优化
CLOSE_WAIT
状态
如果服务器端有大量 CLOSE_WAIT
状态的连接,很有可能是应用进程出现 bug,没有及时关闭连接。
FIN_WAIT1
状态
调整发送 FIN 报文的重试次数,0 相当于 8
net.ipv4.tcp_orphan_retries = 0
FIN_WAIT2
状态
调整保持在 FIN_WAIT2
状态的时间
net.ipv4.tcp_fin_timeout = 60
总结
看到这里,想必你应该对 TCP 连接有了一个大致的了解。现在服务器大多都用了 nginx 做了负载均衡,因此,我们可能需要在此基础上了解一些 nginx 相关的配置原理,这样应该会对我们的服务器性能调优会有更大的帮助。有兴趣的同学不妨可以去了解一下,如果有什么新发现想和作者探讨的,欢迎在下方留言。
有兴趣的话可以关注我的公众号,说不定会有意外的惊喜。