solidSpoon / solidSpoon.github.io.old

欢迎来到 Hide 博客!这里是 solidSpoon 的个人空间,分享技术心得、生活点滴和有趣事物。
https://solidspoon.xyz
0 stars 0 forks source link

有趣的三次握手与四次挥手 1 #17

Closed solidSpoon closed 3 years ago

solidSpoon commented 3 years ago

最近研究了以下 TCP\IP 协议中的三次握手与四次挥手,下面我将我的收获分享给大家。

IP 协议是一种在两点间建立传输的基础协议,可以说几乎所有的其他协议都建立在 IP 所提供的基础服务上进行传输。但 IP 协议是一种不可靠的传输协议,它既不保证一方发送的数据包到达另一方的顺序,也不保证发送的数据包能够送达到另一方,也就是说通过 IP 协议传输可能会发生丢包和延时。在一些需要保证数据完整性的场景中,IP 协议的不可靠性显然是无法被接受的。

TCP 协议全称为传输控制协议(Transmission Control Protocol),它是建立在 IP 协议上的一种传输协议。其目的就是在 IP 提供的不可靠传输服务中,在两点间建立一个可靠的传输连接。其可靠性是建立在「失败重传」这一机制上的,也就是说:任何一方向另一方发送数据,都必须收到对方的确认回应,否则就重新传输。在 TCP 协议中,最著名的莫过于其建立连接时的「三次握手」与断开连接时的「四次挥手」,本文我们就一起探索一下这两个过程。

注意:这里的「三次握手」英文原文是 three-way handshik 或者 three-message handshake,即通过三个步骤 / 三条消息才能建立的握手机制,同理四次挥手也是如此。

三次握手

我们首先来思考一个问题:如何才能确认一个连接是可靠的?其实如果你经常逛知乎的话,应该会见到下面这个讨巧卖萌的解答方式:

「喂,你听得到吗?」
「我听得到啊,你能听到我吗?」
「我能听到你。」

没错,这就是一个简化了的 TCP 连接的建立过程。那么我们来分析一下这一简化的过程;

显而易见,如果我们想建立一个双向的、可靠的通讯,我们必须确认建立连接时双方的接收和发送数据的功能都是可用的,用上 TCP 中的术语,大概是下面这样。

相信你也发现了,为了减少一次通讯次数,我们会把上图的 ② 和 ③ 合并成一次,

你看,经过三步,一个简化的 TCP 连接就建立起来了,之后 A 和 B 就会互相传输数据。但是我们说过 IP 协议会发生丢包和延时,而三步握手仅仅是确认了双方接受和发送数据的功能是可用的,并没有解决 IP 协议丢包和延时带来的问题。那么 TCP 是怎么解决这一问题的呢?

前文提到过,TCP 用来保证可靠性的方法是「失败重传」,我们可以从 A 机器向 B 机器发起连接请求的那条消息(即上图 ①)开始,将 A 发给 B 的所有消息按顺序标上序号,同时也将 B 发给 A 的所有消息按顺序标上序号,这样对方收到发来的消息就能根据序号分辨出顺序和丢包,如果发生丢包就可以要求重传。但在实际传输中,由于各个中间节点的配置不同,一个数据包在传输过程中可能被中间节点分成几份,所以实际中序号的意义是「这是我们所传输数据的第几个字节」。如下图所示

注意这里的序号并不是从 0 开始,其实用 0 表示初始值在编程中是普遍的做法,为什么在这里就不是这样了呢?这里我们考虑一下当 TCP 未能正常建立连接的情况。

我们将发起请求的 A 机器称为客户端,将接受请求的 B 机器称为服务端,通常服务端通过四个数据识别一条连接,即双方的 ip 地址和双方建立连接的端口号。假设客户端首先请求建立连接,然后发送了一个数据包(假设序号为 x)但是这个数据包在传输过程中发生了较大的延时,导致服务端认为连接中断,关闭了连接。紧接着同一个客户端再次发起请求,在刚建立连接的时候,之前延时的那个数据包碰巧到达了,而如果两次连接的端口一样的话,也就是说新连接建立时双方的 ip 地址和端口号都和老链接一样,这个来自老链接的数据包就可以轻松的蒙混过关,被服务端接受下来,这显然是不被允许的。

- 客户端:我想建立连接。
- 服务端:好!
- 客户端:发送第一个数据包 ①
- ......(第一个数据包发生延时)
- 服务端:奇怪怎么没动静了,断了连接吧
- ...

- 客户端:奇怪,怎么断了。我想建立连接。
- 服务端:好!
- 数据包 ① 终于到了!
- 服务端:来数据了,我要收下

因此,每次建立连接的起始序号应该是不同的,也就是说我们需要一种随机数算法,保证生成的随机数在一个数据包的最大生存周期内不会重复。我们用这个随机数作为连接的起始序号,这样结合数据包的四元组(双方建立链接的 ip 和端口),就可以保证在数据包的最大生存周期内,每一个数据包的唯一性,以此避免发生混淆。通常这个起始序号有一个专用的的名字叫做 ISN (Initial Sequence Number)。

在实际情况中,客户端和服务端在建立连接时分别会生成自己的 ISN,然后通过建立连接的报文发送给对方,供对方确认是否丢包。

下面我们来看看 TCP 报文的格式

报文的第一行两个端口号各占两个字节,分别表示了源机器和目标机器的端口号,再加上 IP 报文中双方的 IP 地址即可组成之前所说的四元组。协议的第四行包括了 6 个控制位,这里我们主要关注其中的 SYN, ACK, FIN 这三个控制位。SYN 即 synchronize,在建立连接时使用,也就是我们所说的「听得到吗?」;ACK 表示 acknowledge,表示确认收到了消息,也就是我们所说的「听得到!」;FIN 表示 finish,在稍后讲述断开连接时会使用。

报文的第二行和第三行是序列号,各占 4 个字节。它们就是我们之前说的「序号」,seq 是指所发送数据包中数据部分第一个字节的序号,ack 是指期望收到来自对方的下一个数据包中数据部分第一个字节的序号。TCP 还规定,如果一方发送的 seq 序号为 x,那么就代表它已经收到了从建立连接开始到 x-1 的所有数据,有了这一机制,当接收方发送的确认数据在传输的过程中丢失时,数据发送方也能根据收到的部分确认数据推测出对方收到了多少数据,减少了发送方由于「误会」而造成的失败重传,能有效缓解网络拥挤的现象。

- 服务端:发送以 seq = 101 开始,长度为 10 的数据包。
- 客户端:请发送以 seq = 111 开始的数据包。

- 服务端:发送以 seq = 111 开始,长度为 30 的数据包。
- ...假设数据包被中间节点拆分成 3 个小块

- 客户端:咦,怎么来了一个 seq 121 开始长度为 10 的数据包?  
- 客户端:咦,怎么来了一个 seq 131 开始长度为 10 的数据包?  
- 客户端:终于收到 seq = 111 开始的数据包了,长度为 10,需要回 ACK = 121。  
- 客户端:不对。seq = 121 开始的数据包我好像收到过,需要回 ACK = 131 才对。  
- 客户端:还不对。seq = 131开始的数据包我也收到过,需要回 ACK = 141 才对。

- 服务端:发送以 seq = 141 开始,长度为 10 的数据包。
- 服务端:发送以 seq = 151 开始,长度为 10 的数据包。

- 客户端:ack = 151
- ...丢失
- 客户端:ack = 161

- 服务端:只来了个 ack = 161,我猜 seq = 151 那个数据包也发送成功了。

由于 TCP 报头存在一些扩展字段,所以需要通过长度为四个 bit 的头部长度字段表示 TCP 报头的大小,这样接收方才能准确的计算出包中数据部分的开始位置。

接下来门就看看真正的三次握手。注意在请求连接的数据包中,由于不传输,SYN ACK 被当作数据部分第一个字节。

说完了三次握手,我们再来看看 TCP 断开连接时的四次挥手

四次挥手

有了前面的基础,四次分手就简单的多了。首先是简化版本

看到这幅图可能你也明白了,TCP 的建立连接和断开连接都需要两轮问答,只不过建立连接的时候,② 和 ③ 可以被合并为一次通信。而在断开连接的时候,由于 A B 两台机器通常不会同时发送完数据,所以只能等待每一方发完数据时分别进行一轮确认。接下来看看带有序号的「正式版」

关于四次挥手我看到过一个分手的版本

可能你注意到了,双方最后有 2 个月的过渡期,在四次分手中也有这样的一个过渡期,它的时长为 2MSL。MSL(Maximum Segment Lifetime)中文可以译为「报文最大生存时间」,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。2MSL即两倍的MSL,在此期间,主动分手一方会进入一个名为 TIME_WAIT 的等待状态,也称为 2MSL 等待状态。

当 TCP 的一端发起主动关闭,在发出最后一个 ACK 包后,即第3次挥手完成后发送了第四次挥手的 ACK 包后就进入了 TIME_WAIT 状态,必须在此状态上停留两倍的 MSL 时间,等待 2MSL 时间主要目的是怕最后一个 ACK 包对方没收到,那么对方在超时后将重发第三次挥手的 FIN+ACK 包,主动关闭端接到重发的 FIN 包后可以再发一个 ACK 应答包,并且会重新计时。在 TIME_WAIT 状态时两端的端口不能使用,要等到 2MSL 时间结束才可继续使用。当连接处于 2MSL 等待阶段时任何迟到的报文段都将被丢弃,因此 2MSL 等待状态的另一个用处是防止已失效连接的请求数据包与正常连接的请求数据包混淆而发生异常。

至此我们已经对 TCP 的三次握手与四次挥手有了初步的理解,后文会更加详细的说明在 TCP 的握手与挥手时双方的状态转换。

solidSpoon commented 3 years ago
title: 有趣的三次握手与四次挥手 ①
date: 2018-12-17 21:53:49
tags:
    TCP-ip