谈谈 TCP 的三次握手四次挥手——浅谈 TCP 系列

本贴最后更新于 1929 天前,其中的信息可能已经事过景迁

背景

TCP 的三次握手四次挥手,估计大家都听过。但是真的能把每一步说明白的人比较少。我还记得在之前面试的时候被面试官一顿问,然后一脸懵 B...
都是大学没好好上课 😢,这篇文章就跟大家讲讲到底这三握四挥是在搞什么飞机。

三次握手

握手是指的双方进行连接的操作。


三次握手(图片来自网络)
## 为什么是三次握手 #### 确认通信能力 我们要明白,如果需要进行通信,首先需要保证的是双方都具有发信和收信能力。在不知双方能力状态下进行的通信都是无法保证可靠性和通信效率的。那么通信双方如何确认对方的通信能力呢? 1. A请求B进行连接。(B已确认B的收信能力和A的发信能力) 2. B返回ACK相应。(A已确认双方的收发能力) 3. A返回ACK并建立连接。(B确认双方收发能力) 上面可以看到,至少是进行三次握手,才能确认双方能力。 #### 防止产生脏数据连接 网络通信情况复杂,不可能保证每一消息都能正常到达其目的地。 在TCP连接中,TTL的网络报文的生存时间一般都会比TCP的连接超时时间要长。这样就有可能出现一个问题。A在发送第一次连接请求时,可能网络拥塞,导致数据包未短时间内到达。到达超时时间后,A又发送了一次连接请求,这次正常进行连接。连接结束断开后,A的第一次连接请求到达B,B返回ack。如果是两次握手,A通过当前的状态,直接拒绝B的请求,但B会单方面认为连接已经建立,实际上并不是...

每一次握手到底是传输了什么?

想知道除了 ip 和端口号,每次传输的信息是什么,我们首先得知道 TCP 传输信息的结构。


TCP数据包结构(图片来自网络)

第一次握手

  • 首先会将 TCP 的 header 部分的控制位(上图的 flags)中的 SYN 置为 1。表示希望建立连接。
  • 还有一个重要的数据,seq,即序号。这个东西是干嘛用的呢?这个涉及到 TCP 安全性和可靠性,与 ack 即确认号紧密相关。TCP 在传输数据时,如果数据比较大,会进行拆分操作,将大数据拆成一个个小的数据包。序号就是在这个时候用的。我们必须要知道这个包的顺序是什么,才能把真正的数据在服务端还原。seq 的顺序并不是从 0 或者 1 开始的,而是一个随机值。因为如果序号从 1 开始,那么整个通信的过程非常容易被预测。正因为是随机的,所以对方不知道你的 seq,所以我们需要在开始收发数据之前,将 seq 先发送给对方。序号的初始值的传递就是通过 SYN=1 的操作传递的。

客户端此时处于同步状态,即可以建立连接。服务端处于监听状态。

第二次握手

  • 标志位。因为已经收到了客户端的建立连接请求。所以必须要发送 ack,以告知客户端自己已经收到消息。所以 ACK 的标志位要置为 1,且 SYN 也是 1,因为还未建立连接。
  • seq。同第一次握手一样,也是一个随机值(TCP 为全双工,所以双方都需要保留 seq 方便处理数据包)。
  • ack。是对客户端发过来的序列号进行计算得到的

服务端处于 SYN 接收状态。

第三次握手

  • 标志位。此时只需要 ACK=1。SYN 已经不需要了,双方已经同步完 seq 等信息。
  • seq。可以说是第二次握手收到的 ack。
  • ack。是对第二次握手收到的序列号进行计算得到的。用以告知已收到二次握手信息。

客户端处于连接建立状态,服务端收到信息之后也会进入连接建立状态,双方可以进行通信。

四次挥手

TCP 连接在数据传输完成之后需要关闭,不然会一直占用系统资源。TCP 的连接关闭需要四次通信才行,分手也是个麻烦事儿。

为什么四次才能断开连接呢?

分手这件事情,两边都说明白,分别断开即可。但是想要确认对方都要断开,那么一次两次是不够的。


四次挥手(图片来自网络)

我们还是拿 A、B 来举例。假设 A 要主动断开和 B 的连接。

  1. A 发送断开请求。(需要等待 B 的回复,不然 B 未收到消息 就单方面的断开有点不负责任。超时重传)
  2. B 收到请求并回复 A。(B 收到请求后,很伤心,但是没有办法,只能断开连接,但是 B 是被动的接收断开,所以需要通知其应用程序做关闭准备)
  3. B 这边准备完了,通知 A 可以断开了。
  4. A 回复 B,我收到消息了,断开连接吧。

请求发送的信息是什么?

下面是抓包看到的数据
tcpfin.png

关闭由哪方开始?

答案是哪边都可以,无论是客户端还是服务器端,都可以主动发起关闭请求。
发起关闭请求的前提是数据发送完毕,不一定非得等待对方确认完成。

第一次挥手

假设客户端发起关闭请求。那么客户端发完消息后,会进入 FIN_WAIT 阶段。此处已经无法再发送应用程序消息,只能处理关闭相关信息。

第二次挥手

服务端收到第一次挥手消息后,返回收到消息的 ack。服务端会进入 CLOSE_WAIT 阶段。
这个阶段是等待关闭阶段,通知应用程序发送剩余数据,处理现场信息,关闭相应资源

第三次挥手

你应该有点好奇为什么第三次挥手和第二次挥手都是同一个服务。第二次主要是一个 ack 响应,第三次主要是一个服务端关闭通知消息。两者目的不同。
等到三次挥手完毕,那么服务端会进入 LAST_ACK 状态,即等待最后客户端(主动发起关闭的一方)的 ack 确认。

第四次挥手

第四次挥手是主动发起关闭的一方 A,对被动关闭一方 B 的 FIN 消息的确认。
第四次信息发送完成后,A 会进入 TIME_WAIT 阶段,而不是直接删除套接字。具体原因我们在下面讲。
收到第四次挥手信息后,B 会直接进行关闭操作。

为什么会有 TIME_WAIT?

还是那句话,网络并不是一个理想世界,任何异常情况都有可能发生。
为了保证 TCP 连接能够正常关闭,主动发起关闭方不能直接删除套接字,而是需要经过一段时间等待。这个时间一般是 2MSL(Maxumun Segment Lifetime 最大报文生存时间)。
原因如下

  • 确认被动关闭方能够正常进入关闭状态。假设 B 未收到 A 最后的一个 ack,会再次发送 FIN 消息(第三次挥手),如果 A 已经 CLOSE 了,那么 B 就会一直重试发送。所以 A 必须要进行一段时间的等待。
  • 防止失效请求。假设 A 关闭了此次连接,又重新在原来的端口号上开启了新的连接。原来在网络上发送的一些包(已失效但未超过 ttl)到来之后,无法进行区分是否是正常的包,导致数据混乱。

举个例子来证明没有 TIME_WAIT 的情况。

  1. A 断开了,并且删除了套接字。
  2. B 没有收到 A 的最后一次 ack,导致 FIN 重发。
  3. A 重新开启了新的套接字新的连接,但是这个套接字和之前删除的套接字拥有相同的端口号。
  4. B 后来重发的 FIN 会错误的跑到新的套接字,导致 A 开始执行断开操作。

所以 TIME_WAIT 也是为了防止上面的误删除。

总结

在并发量很高的时候,熟悉 TCP 的原理和参数变得尤为重要。
我们关注的不仅仅是几次握手 几次挥手,也应该关注一些细节,细节决定成败。更应该多问问为什么如此设计,能解决什么问题。

后续

后续主要是与大家分享讨论一下对 TCP 的滑动窗口机制和可靠性保障的理解。如果有时间,可以跟大家分享一下 TCP 的参数等配置相关内容。

  • TCP
    32 引用 • 38 回帖 • 2 关注
  • 网络
    138 引用 • 177 回帖 • 4 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...