总结一下这个基本知识点,确保自己真的理解正确了
先来看 TCP 状态图和 TCP 包格式
▲ TCP 状态图
▲ TCP 包结构
上面转自伯乐在线(伯乐在线转自 优快云 的一篇博文)的 TCP 包结构图其实存在错误。其中的 PST 应该为 RST,意为 Reset connection,后面介绍四次挥手的时候再介绍 RST。对于规范性的技术,最好是参考维基百科或 RFC 之类的文档。尽信书不如无书,我开始也没能发现
三次握手
所谓 TCP 服务器端,是指其 TCP 协议栈会监听某个端口上的连接事件。
- 服务器开始处于监听状态。当客户端要连接服务器端时,客户端首先会发送一个 TCP 消息,其 SYN=1,并生成一个随机的 seq number,我们记为 seq=x。在用 Wireshark 分析 TCPDUMP 时,其显示的 seq 为 0,因为 Wireshark 为了用户方便,显示的是 seq 的相对值。在发送 SYN 之后,客户端的 TCP 栈便处于 SYN SENT 状态。
- 当服务器端收到客户端发来的 SYN 消息(SYN 位为 1 的 TCP 消息)之后,便回复了一个 TCP 消息,其 SYN=1,ACK=1,seq=y,ack=x+1。ACK 表示确认,ack 的值为其所确认的 SYN 包中 seq 值加 1。seq 值为一个随机值。因为 TCP 是一个双工的协议,两个方向都可以传输数据,所以需要在两个方向都确认可以传输数据,因此需要双方都发 SYN。这是服务器端的 TCP 协议栈对于当前连接来说处于 SYN RCVD 状态。
- 当客户端收到服务器端的 SYN,ACK 时,客户端便会返回一个 ACK 以确认这个 SYN 包,ack 便为 y+1。
- 当连接建立之后,对于 TCP 而言,就无所谓客户端服务器端
四次挥手
当 TCP 连接要被断开的时候,便会有所谓的四次挥手的过程。
- 用于断连接 TCP Flag 是 FIN。断连接时,一发起方放出一个 FIN 包,seq 值为一个随机值,这里记为 u。
- 对端在收到 FIN 之后会用 ACK 响应这个 FIN。所以 ack=u+1
- 接下来另一方也会发出 FIN 包。这还是因为 TCP 是双工的,所以双向都要关闭连接。在上图中,另一方发的 TCP 消息是 FIN + ACK。如果是演示四次挥手的过程,FIN 和 ACK 是分开的。在真实 TCP 协议栈的实现中,多数情况 FIN 和 ACK 是同时发出,即上图所演示的那样。如果另一端同时发送 FIN 和 ACK,那上图的第二个消息,即 ACK 消息其实是不存在的。所以,很多情况 TCP 断连接的过程是“三次挥手”。
- 同样,在客户端收到服务器端的 FIN+ACK 包之后,其也会回复一个 ACK 已确认之前的 FIN 包。
在断连接时,有时也能看到 RST 包。这种情况常见于在断连接开始之后,被动的一方还有数据没有发送完毕。这时这一方便会使用 RST 在断连接的同时发送剩余的数据。这些都是 TCP 协议栈(或者说是系统 Kerel)去根据不同情况作出的选择。
附:CLOSED_WAIT 和 TIME_WAIT
常见的和关闭连接相关的 TCP 状态有 TIME_WAIT 和 CLOSED_WAIT。如果常有大量的 TCP 短连接到 TCP 服务器上,那么就会看到很多的连接处于 TIME_WAIT 和 CLOSED_WAIT。
过多的 CLOSED_WAIT 或 TIME_WAIT 连接都是一件危险的事情。
过多的 CLOSED_WAIT
通常情况下 CLOSED_WAIT 状态持续的时间不应该很长。如果有连接长期处于 CLOSED_WAIT 往往以为着程序错误。曾经使用 Apache HttpComponents HttpClient,发现服务器端关闭 TCP 连接之后其不会自动关闭连接,必须手动执行连接关闭操作。如果不注意变化造成很多 CLOSED_WAIT 状态的连接(严格来说不应该说连接出于什么状态,而是 TCP 协议栈处于什么状态。用 netstat 查看的其实是本方协议栈的状态)。
过多的 TIME_WAIT
如上图所示,TIME_WAIT 状态在一段时间之后就会变为关闭状态。但是过多的 TIME_WAIT 也会消耗资源。一些系统设置可以降低大量 TIME_WAIT 对系统的影响。比如可以配置系统在连接数不够的时候重用 TIME_WAIT 连接。
vi /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1