最近在学习 HTTP 长连接,发现需要复习一些 TCP 连接的建立、关闭的过程,做些笔记。
TCP 报文头
先来看看 TCP 报文头的格式:
- source port/destination port 分别代表源端口和目标端口
- sequence/acknowledge number 用来标记发送和接收的字节数
- data offset 占 4 位,代表报文头中的字(32位)数,如果没有 options,则为固定值5
- Flags,共 9 位
- NS、CWR、ECE、URG,不懂
- ACK,设置了后表示 acknowledge 字段生效
- PSH,要求将缓存的数据推送给接收方
- RST,重置连接,比如接收方已经关闭连接,收到迟到的报文,则会重置报文
- SYN,三次握手第一次,代表同步 sequence number
- FIN,四次挥手时的结束报文
- Window size,拥塞控制中的窗口大小
- Checksum,校验码,用于传输检测过程中的错误
- Urgent pointer,不懂
- Options,一般在三次握手、四次挥手中用到。不懂
三次握手
三次握手过程为:
- 客户端随机生成一个 sequence number,并发送 SYN 报文到服务端,请求连接
- 服务端发送 SYN+ACK,在应答请求的同时,也随机生成一个 sequence id,请求同步
- 客户端应答,服务端收到应答后双方建立连接。
正如 SYN 标志的含义,三次握手的过程在建立连接的过程中完成了自身初始 sequence number 的同步。使用随机生成的 sequence number 是为了防止在网络中滞后的报文影响新建立的连接。
三次握手的重要问题是:为什么要三次?因为信道不可靠。考虑两次握手。假设客户端发送的第一个 SYN 在网络中滞留了,客户端因此重发 SYN 并建立连接,使用直到释放。此时滞留的第一个 SYN 终于到了,根据两次握手的规则,服务端直接进入 ESTABLISHED
状态,而此时客户端根本没有连接,不会理会服务端发送的报文,白白浪费了服务端的资源。
事实上,只要信道不可靠,双方永远都没有办法确认对方知道自己将要进入连接状态。例如三次握手,最后一次 ACK 如果丢失,则只有客户端进入连接状态。四次、五次、多少次握手都有类似问题,三次其实是理论和实际的一个权衡。
四次挥手
要断开连接需要“四次挥手”,可以由客户端发起,也可以由服务端发起,步骤如下:
- 发起方发送 FIN 报文,代表断开连接
- 接收方响应 ACK 报文,并在自己发送完未处理的报文后发送 FIN 报文
- 发起方接收 ACK 报文后等待接收方的 FIN 报文,收到后发送 ACK 报文,自己进入 TIME_WAIT 状态,等待 2MSL 后关闭连接
- 接收方收到 ACK 报文,关闭连接
为什么需要 4 次挥手?一般会说因为连接是双方的,每一方关闭连接时需要 FIN+ACK。因此一共 4 次。而从上图来看,主要是因为接收方发送 ACK 和发送 FIN 之间可能有间隔,接收方需要等待应用程序处理结束后发送 FIN 报文。如果 ACK+FIN 一起发送,则就变成三次挥手了。
在做短连接做压测的时候经常会出现大量端口处理 TIME_WAIT
状态,导致无端口可用。为什么需要这个状态?
- 防止滞后的报文被后续建立的连接接收,因此结束连接前先等待 2MSL 的时间。(MSL 是最大的报文存活时间,一来一回可以认为与上次连接相关的报文都不在网络中了)
- 确保接收方已经正确关闭连接,考虑发起方最后一次 ACK 滞留,则接收方一直处于
LAST_ACK
状态,而不会关闭连接。那么此时发送方重新建立连接 SYN,则由于序列号不同,处于LAST_ACK
的接收方会响应RST
报文。即连接未正确关闭导致后续连接无法建立。
状态转换
附一张状态转换图作为参考: