TCP 三次握手 / 四次挥手

Posted: 11.12.2019

#网络协议 #TCP #面试问题 - 网络

介绍

在讲三次握手与四次挥手之前,我们势必要先讲一下 TCP 本身。

TCP 是 OSI 模型第四层(传输层)的协议,位于第三层(网络层)的 IP 协议之上。

TCP = Transmission Control Protocol,传输控制协议,目的自然是为了控制传输。

TCP 是连接导向的(connection oriented),因此在传输数据前,需要建立会话(session)。

而建立会话的方式,就是三次握手。三次握手后,TCP 便会建立一个全双工(full-duplex)的通信,然后开始传输数据。

但是在讲三次握手与四次挥手之前,我们必须先讲一下 TCP 的报文格式。

TCP 报文格式

TCP header

Source PortDestination Port 分别指明了发送和接受数据的端口。

接下来就是三次握手中需要用到的序列号(Sequence Number)和确认号(Acknowledge Number)。

接下来的 Header Length 指明了 TCP 报头的长度,和握手挥手没关系,这里就不细讲了。

Reserved 则是保留起来,未来可能会用到,也和握手挥手没关系,不细讲了。

接下来,非常重要的是六个 flag 里面的,ACK 和 SYN 这俩 flag(flag 的意思就是设置为 true 或者 false,中文是标志)。

我们经常可以看到有的文章在讲三次握手的时候,一会儿用大写的 ACK,一会儿又用小写的 ack,是不是很奇怪。是他们弄错了吗?其实不是,大写的 ACK 指的是这六个 flag,而小写的 ack 则是指的上面那个 32 bit 大小的确认号。如果大小写 SYN 和 ACK 分开用,说明那个人其实是很懂的。

ACK 这个 flag 如果设置成 true,说明确认序号有效。

SYN 这个 flag,只有在发送方和接收方分别发送第一个报文(packet)的时候,才会设置为 true。

FIN 这个 flag,指明了发送方已经没有数据需要发送了,要结束 TCP 链接。

剩下的部分在三次握手和四次挥手中不会涉及到,因此不细讲了。想要了解更多的话,可以去看这篇文章:TCP/IP 数据包报文格式(IP包、TCP报头、UDP报头)

三次握手

流程

我们先来看一下三次握手的流程:

TCP HANDSHAKE

  1. 首先,要建立 TCP 连接,于是发送方给接收方发送了一个报文,这个报文的 SYN 标志被设置成了 1,说明要开始握手了
  2. 我们假设这个时候的序列号为 x(这说明发送方的数据是从 x 的位置开始发送的)
  3. 接收方收到了来自发送方的 TCP 报文。因为窗口大小(Window Size)为 1,所以他其实收到的就是序列号为 x 的 segment,而且就只有这一个 segment。然后他想要让发送方知道自己已经收到报文了,因此就发送了一个新的报文(只有一个报文,并不是很多文章里写的两个)给发送方
  4. 在这个报文里,ACK 和 SYN 被设置为 1
  5. 确认号(ack)被设置为 x+1,这意味着接收方现在收到了 x,所以他希望发送方接下来发的玩意儿要从 x+1 开始
  6. 并且接收方随机生成了一个新的序列号 y,作为这个报文的序列号,发给发送方。
  7. 发送方接收到了来自接收方的报文,但这还不太够,还需要返回一个确认给接受方
  8. 这里三次握手已经要结束了,所以这次发送的报文里,SYN 并没有被设置成 1,但是仍然需要确认,因此 ACK 被设置成了 1
  9. 刚才来自接收方的报文里写了,ack = x+1,说明接收方希望下面收到 x+1 的玩意儿,那么就满足他呗,把当前报文的序列号设置成 x+1
  10. 不过对面设置了 ack,我这里也得设置啊,刚才他发了 y,那么我自然希望下一个收到的是从 y+1 开始的,于是把确认号设置成了 y+1。
  11. 接收方收到了来自发送方的报文
  12. TCP 连接建立,双方可以进行数据传送了。

如果只有两次握手

如果只有两次握手的话,就会出现死锁。

考虑两个计算机 sb 和 wdnmd 之间的通信。

  1. 假定 sb 给 wdnmd 发送一个连接请求分组
  2. wdnmd 收到了这个分组,并发送了确认应答分组
  3. 然后假设 sb 发送给 wdnmd 的确认丢了
  4. 按照两次握手的协定,sb 认为连接已经成功地建立了,可以开始发送数据分组
  5. 可是,wdnmd 在 sb 的应答分组在传输中被丢失的情况下,不知道 sb 是否已准备好,不知道 sb 建立什么样的序列号
  6. 在这种情况下,wdnmd 认为连接还未建立成功,将忽略 sb 发来的任何数据分组,只等待连接确认应答分组
  7. 而 sb 在发出的分组超时后,重复发送同样的分组
  8. 这样就形成了死锁

举个🌰:假设你和女朋友出去开房,你先去开房,然后把房号告诉女朋友。

  1. 你开了间房,房号为 110,然后你发短信给女朋友,说 ”房号是 110“
  2. 你女朋友收到了房号,但还要再确认一下,不然走错就尴尬了,所以她给你回了条短信,问 ”房号是 110 吧“
  3. 你收到了这条短信,于是发了条确认过去,”没错,房号就是 110 “
  4. 网不太好,你的女朋友没收到这条短信
  5. 但从你的角度来说,你觉得你女朋友应该已经确认房号了,就直接过去了
  6. 然而你女朋友没有收到你的确认,不能确定房号就是 110,于是一直在等你的回复
  7. 你到了房间里,等了好几个小时,女朋友都没来
  8. 然后你意识到,自己被放鸽子了。wdnmd

在这个例子里,只有你自己觉得连接建立了,但你女朋友不这么觉得,这就是只有二次握手的后果。

四次挥手

流程

TCP 4-way handshake

  1. 首先,客户端要通知服务器,咱俩拜拜了,因此停止发送数据,发送了一个报文,并且把 FIN 标志设置成 1
  2. 并且在这个报文里,客户端指定了序列号为 u(等于前面已经传送过来的数据的最后一个字节的序列号+1)
  3. 此时,客户端进入FIN-WAIT-1状态
  4. 服务器收到连接释放报文,要发一个确认报文来通知客户端自己知道了。于是这个报文的 ACK = 1,确认号(ack)就是 u+1
  5. 此时,客户端进入 CLOSE-WAIT 状态:这是一种半关闭的状态,意味着客户端已经没有数据要发送了,但服务器若发送数据,客户端依然接受
  6. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文。因为是连接释放报文,所以 FIN = 1,并且 ack 和确认报文的 ack 一样
  7. 此时客户端进入TIME-WAIT状态
  8. TCP 连接还没有释放,必须经过(2*最长报文段寿命的时间)后,当客户端撤销 TCB 后,才关闭
  9. 服务器只要收到了客户端发出的确认,立即进入关闭状态(因此服务器结束 TCP 连接的时间要比客户端早一些)

为什么是四次挥手?

因为三次握手的时候,ACK 和 SYN 其实是在一个报文里发送的(只不过是分别设置了对应的 flag)。

而关闭 TCP 连接时,当服务器收到含有 FIN 报文后,它知道客户端已经没有数据了。但服务器自己可能还有数据没有发送完毕,不会立即关闭 SOCKET,所以没办法立刻回复一个 FIN 报文,只能先回复一个ACK报文,告诉客户端收到了。只有当服务器端所有报文都发送完了,才可以发送 FIN。

为什么需要经过 2MSL?

MSL(Maximum Segment Lifetime),最大报文段生存时间。2MSL 就是一个发送和一个回复所需的最大时间

虽然按道理,四个报文都发送完毕,我们可以直接进入 CLOSE 状态了,但是我们必须假设网络是不可靠的,有可以最后一个 ACK 丢失。

服务器如果没有收到来自客户端的确认报文,将不断重复发送 FIN 片段。所以客户端不能立即关闭,它必须确认服务器接收到了它的确认报文。

所以 TIME_WAIT 状态用来重发可能丢失的 ACK 报文。

客户端会在发送出 ACK 报文之后进入到 TIME_WAIT 状态。它会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到 FIN报文,就说明服务器没有收到它的确认报文,因此客户端会重发 ACK 报文并再次等待 2MSL。

参考资料

TCP Header and Applications

TCP/IP 数据包报文格式(IP包、TCP报头、UDP报头)

TCP三次握手和四次挥手过程

TCP三次握手及四次挥手详解及常见面试题