TCP 三次握手与四次挥手
TCP 连接的开启和结束都需要基于状态机进行多次交互,这里对 TCP 的三次握手和四次挥手流程进行分析。
TCP 三次握手
握手流程
为了建立一个可靠的 TCP 连接,需要进行三次握手。三次握手整体流程如下图所示:
第一次握手(SYN):建立 TCP 连接时,首先由客户端发送一个数据报文,设置了 SYN 标记,并在 TCP 固定头部中设置随机的初始化序列号 Seq = x。发送后客户端进入 SYN-SENT 状态。
第二次握手(SYN-ACK):服务器收到带有 SYN 的数据段后,发送设置了 ACK、SYN 标志的数据报文。ACK 设置确认号 x + 1,与客户端发送的序列号 Seq = x 对应。SYN 对应地设置服务端的随机初始化序列号 Seq = y。发送后服务端进入 SYN-RCVD 状态。
第三次握手(ACK):客户端收到服务器的 SYN-ACK 数据报文后,发送 ACK 数据报文。ACK 设置确认号 y + 1,与服务端发送的序列号 Seq = y 对应。发送后客户端进入 ESTABLISHED 状态,可以向服务端发送应用数据。
服务端收到 ACK 数据报文后,进入 ESTABLISHED 状态,可以向客户端发送应用数据。至此,双方均确认链路畅通,且对方有连接意愿。
三次握手原因
为 TCP 连接过程设置三次握手的原因包括几点:确认通信双方收发能力正常;同步双方序列号;确认双方通信意愿。
确认通信双方收发能力正常
TCP 需要在不可靠的信道上建立可靠的字节流传输信道,因此需要确认双方能正常通信才能认定为成功建立 TCP 连接。这个检查需要确认的部分包括:客户端收发能力正常、服务端收发能力正常。
服务器成功收到 SYN 报文,即可确认客户端发送能力正常,且服务器接收能力正常,此时服务端还未确认客户端的接收能力、服务端的发送能力。
客户端收到 SYN-ACK ,可以确认服务器成功收到 SYN 报文并发送 SYN-ACK 报文,这就证明客户端、服务的的收发能力都正常,因此对客户端来说 TCP 连接能力建立成功,可以进入 ESTABLISHED 状态,开始向服务器发送数据。
然而发送 SYN-ACK 报文后的服务器还不能确认客户端接收能力和服务器发送能力是否正常,因此需要客户端在发出回应以帮助服务器确认客户端接收能力和服务器发送能力正常。这就是第三次握手发送的 ACK 报文,无论客户端是否需要发送数据都需要发送。服务器收到这个 ACK 报文后,就可以确认双方收发能力都正常,进入 ESTABLISHED 状态,此时可以开始向客户端发送数据。
同步双方序列号
在 TCP 连接中,可能存在大量正在网络中传输的报文,这些报文可能出现丢失、重复等问题,因此通信双方需要有重传机制来保证连接正常。这个重发机制需要了解对方的接收进度来提高效率,这个进度是通过 TCP 报文头部的序列号、确认号来实现的。为了收到对方使用的序列号并确认对方认可自己使用的序列号,就至少需要向对方发送一条报文、从对方收到对这条报文的确认。因此在正常的连接流程下,服务器需要第三次握手才能完成序列号同步,并完成 TCP 连接建立。
确认双方通信意愿
由于网络可能存在的延迟,服务器收到的 SYN 消息可能已经过期,客户端已没有与服务器建立连接的意愿。此时客户端对于服务器发回的 SYN-ACK 消息可能直接丢弃。如果仅通过两次握手,服务器就判定为 TCP 连接成功建立,那么服务器就可能持有客户端不存在的连接信息,浪费服务器资源。通过第三次握手,服务器可以在客户端未发送对应的 ACK 报文时通过超时机制认定连接失败,从而在收到过期 SYN 消息时及时发现。
TCP Fast Open(TFO)优化
TCP 连接成功建立需要经过三次握手,这就导致至少在第三次握手时才能携带应用数据,这导致至少 1-RTT 的延迟才能进行应用层通信。
如果在第一次握手 SYN 报文中就携带应用数据,并在第二次握手 SYN-ACK 报文中直接返回处理结果,就可以减少这 1-RTT 的延迟,这在建立短连接的场景下非常有吸引力。这种在 SYN 报文中携带数据,服务器读取这些数据并由应用层处理后将返回数据在 SYN-ACK 报文中返回的优化就是 TCP Fast Open。
在不支持 TFO 的服务器上,也可以通过抛弃 SYN 包中的应用数据来回退到到普通的 TCP 三次握手流程,因此 TFO 具有较好的兼容性。但在每个连接上都使用这种优化会增加对服务器发起 DoS 攻击的风险。在不使用 TFO 的服务器上,发起 DoS 攻击可以使用 SYN 泛洪攻击消耗用于维持半开连接的资源。而使用 TFO 的服务器除了维持半开连接的资源外,还需要对每个 SYN 包中的应用数据进行处理,额外消耗应用层资源,从而放大了 DoS 攻击的威力。因此,需要使用一定的安全机制来减少 TFO 的安全风险。TFO 使用 TFO Cookie 来降低这种风险。
TFO Cookie 要求客户端与服务器在首次连接时使用 TCP 三次握手流程,在这个过程中通过额外的选项请求服务器生成一个 TFO Cookie,并将这个 TFO Cookie 保存在客户端本地。在之后的连接中,需要客户端携带 TFO Cookie 来使用 TFO 优化。这样可以确认与客户端建立过有效的 TCP 连接。
服务器在收到带有 TFO Cookie 的 SYN 报文时,会对 TFO Cookie 进行检查,验证其签名、时间戳、IP 地址等信息,确认其有效性。检查通过时就可以使用 TFO 将应用数据交给应用层处理,并在 SYN-ACK 报文中返回数据。检查不通过时就回退到 TCP 三次握手流程上。
TCP 四次挥手
结束流程
为了优雅断开 TCP 连接,需要通过四次挥手。四次挥手流程如下图所示:
第一次挥手(FIN):主动关闭方发送设置了 FIN 标志的 TCP 报文,携带序列号 Seq = u,进入 FIN-WAIT-1 状态。
第二次挥手(ACK):被动关闭方收到 FIN 报文后,返回 ACK 报文,确认序列号 u + 1,进入 CLOSE-WAIT 状态。
第三次挥手(FIN):被动关闭方发送设置了 FIN 标志的 TCP 报文,携带序列号 Seq = v,进入 LAST-ACK 状态。
第四次挥手(ACK):主动关闭方收到 FIN 报文后,返回 ACK 报文,确认序列号 v + 1,进入 TIME-WAIT 状态,等待 2MSL 后进入 CLOSE 状态正式关闭连接并释放资源。
被动关闭方收到 ACK 报文,进入 CLOSED 状态正式关闭连接并释放资源。
四次挥手原因
由于 TCP 是全双工通信,正常的关闭流程需要保证:双方都没有数据需要再发送了。这就要求双方都发送一个 FIN 报文并收到对该报文的 ACK 确认。这与建立过程中三次握手需要的 SYN-ACK 流程类似,需要至少三次才能完成确认。不同之处在于,第二次挥手完全由 TCP 协议自动完成,而第三次挥手需要应用程序介入,且中间可能还有其他数据发送,因此第二次挥手和第三次挥手在正常情况下不能合并在同一数据包中发送。
TIME-WAIT 状态等待 2MSL 是为了确认被动关闭方收到 ACK 报文,成功关闭连接。 极限情况下,进入 TIME-WAIT 状态的 ACK 报文在 1MSL 到达被动关闭方,同时被动关闭方发送了一个 FIN 重传。这个 FIN 重传最多经过 1MSL 到达主动关闭方,这就是进入 TIME-WAIT 状态到网络中该连接的报文消失的最长时间。主动方收到 FIN 重传会重置 TIME-WAIT 计数器再等待 2MSL。这样,在重传超时时间小于 MSL 的情况下,可以保证 TIME-WAIT 结束后该连接的报文在网络中消失。而出现 MSL 后仍有重传请求的极端情况下,主动方的 RST 机制可以帮助被动关闭方通过异常流程关闭连接。
在 TIME-WAIT 状态下,收到 ACK 重传导致的 RST 报文时,主动关闭方可以明确被动关闭方已关闭连接,并关闭重传机制。等待 2MSL 保证网络上可能的 RST 报文彻底消亡,避免干扰相同四元组的新连接。