TCP/IP入门经典 chapter6 传输层
问题驱动
- TCP 协议是不限制一个特定的连接(两端 socket 一样)被重复使用的。那如果这条连接突然断开重连后,新的TCP连接 怎么样识别数据包到底是不是之前旧链接重发或者延迟的包呢?
- 客户端发送初始化请求SYN,因网络问题堵塞,客户端没有接收到响应于是重发SYN,结果旧的SYN又到达了服务端,于是服务端响应了;这种情况下造成的混乱tcp如何解决?
- 在连接初始阶段,客户端(以下简称C端)和服务端(S端)如何知道对方将发送的第一个数据报的序列号(seq)呢?同时保证己方知道了对方知道了自己的序列号
- 有点绕,这么理解:C端“告诉”了S端将发送的序列号,S端到底有没有收到呢?不确定,所以必须有个机制使得C端得知自己的信息已被成功接收,才能确保连接可靠,很繁杂,但网络的世界总是不可预知的,才美的一塌糊涂zzz
前置知识
Tcp 三次握手图示
Tcp 三次握手图示
SYN/ACK 代表 tcp报文中【控制标记】的六个字段之二,分别意为:(TCP报文或其他前置知识见【TCP个人浅见-导学篇】)
| SYN | ACK | |
|---|---|---|
| 含义 | 为1时表示序列号将被同步,说明这是个连接的开始 | 为1时表示“确定号”字段是有意义的 |
seq代表 tcp报文中【序列号】ack代表【确认号】
| seq | ACK | |
|---|---|---|
| 含义 | 当SYN不为1时,这是当前数据分段第一个字节的序列号;为1时,这个字段是初始序列值(ISN)用于对序列号的同步,第一个字节的序列号为ISN+1 | 用于确认已经接收到的数据分段,其值是接收计算机即将接收的下一个序列号,即下一个接收到的字节序列号+1 |
动态图解

庖丁解牛
回答第一个问题
TCP 协议是不限制一个特定的连接(两端 socket 一样)被重复使用的。那如果这条连接突然断开重连后,新的TCP连接 怎么样识别数据包到底是不是之前旧链接重发或者延迟的包呢
关键点:时期区分、唯一起点(ISN -- initial sequence number)
时期区分
简言之,就是需要将建立连接的过程与连接正常的过程区分开,原因很简单,初始化连接过程有一些类似【选择唯一起点序列号】的特殊操作,而连接通信只需要正常的确认应答机制即可(细节见【tcp个人浅见-连接过程篇】)
其实就像我们写业务代码if-else要找标识开关,在tcp报文中也存在着这样的“开关”--【控制标记】中的SYN,当开始三次握手时,发送端将请求报文中的SYN设为1,表示这是个连接请求,接收端收到后同样将SYN设为1,也表示这是个对连接请求的响应,这样,我们就达到了【时期区分】的目的了
小结:seq(序列号)字段存储的32位数据,在三次握手阶段(SYN=1)被称为SYN,不对应数据,正常开始传输数据的首个序列号一般为ISN+1
唯一起点
“怎么样识别数据包到底是不是之前旧链接重发或者延迟的包呢?” -- 唯一ISN(初始序列号)机制
要知道我们对数据包的标识是seq(序列号),也就是报文头上的32位2进制,注意既然是在连接初始化阶段,自然不会发送真正的数据,所以在SYN=1时我们将seq字段中存储的数据称为ISN( initial sequence numbe 初始序列号),真正参与数据传输的第一个序列号应该是ISN+1;
应对建表的概念,如果我们对键设置自增,是不是这个问题就解决了?同样的,如果我们对每次三次握手的ISN的选取采用自增,是不是就成了?
在TCP软件中(tcp只是规范,在我们电脑中的TCP实现软件才是真正建立连接完成通信的角色)存在初始序列号生成器,当新连接建立时它就会生成一个新的32位的ISN;这个生成器用32位长的时钟,每4us 增长一次,因此 ISN 会在大约 4.55 小时循环一次
(2^32位的计数器,需要2^32*4 µs才能自增完,除以1小时共有多少µs便可算出2^32*4 /(1*60*60*1000*1000)=4.772185884 )
而一个段在网络中并不会比最大分段寿命(Maximum Segment Lifetime (MSL) ,默认使用2分钟)长,MSL 比4.55小时要短,所以我们可以认为 ISN 会是唯一的。
此时,双方就都有自己的ISN了
三次握手避免旧的数据报问题
客户端连续发送多次 SYN 建立连接的报文,在网络拥堵等情况下:
- 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;
- 那么此时服务端就会回一个
SYN + ACK报文给客户端; - 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送
RST报文给服务端,表示中止这一次连接。
如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:
- 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是
RST报文,以此中止历史连接; - 如果不是历史连接,则第三次发送的报文是
ACK报文,通信双方就会成功建立连接;
所以, TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。

小结
避免数据报来自于前一个连接产生的延迟数据包,每个连接采用唯一起点序列号,为ISN,由ISN生成器以32位长时钟每4us自增的规则生成;再结合三次通信,如果是错误数据报,则重置,若是正确,则建立连接
回答第二个问题
在连接初始阶段,客户端(以下简称C端)和服务端(S端)如何知道对方将发送的第一个数据报的序列号(seq)呢?同时保证己方知道了对方知道了自己的序列号
告知起点
这是握手为什么是“三次”的核心概念,在握手阶段(假设现在不知道要三次),C端告知S端自己的ISN后,S端自然要回应一个【确定收到】,以及发送自己的ISN给C端;而C端收到后自然也要告诉S端以及收到了对方的ISN
1) A --> B SYN my sequence number is X 2) A <-- B ACK your sequence number is X 3) A <-- B SYN my sequence number is Y 4) A --> B ACK your sequence number is Y
2与3都是 B 发送给 A,因此可以合并在一起,因此成为three way (or three message) handshake(其实翻译为三步握手,或者是三次通信握手更为准确)
因此最终可以得出,为了达到告知起点的目的,三次握手是必须的。
小结
- 三次握手才可以同步双方的初始序列号
证明一:谢希仁著《计算机网络》第四版中,讲 “三次握手” 的目的是 “为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”
谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段” 的产生在这样一种情况下:client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。”
证明二:RFC793 ( TCP 的协议 RFC)讲到,TCP 需要 seq 序列号来做可靠重传或接收,而避免连接复用时无法分辨出 seq 是延迟或者是旧链接的 seq,因此需要三次握手来约定确定双方的 ISN(初始 seq 序列号)。
总结
以上是原因,就“面向面试”编程而言,自然不能balabala说这么多,所以总结浓缩成面试版,但最好搞懂原理,才好忽悠的胸有成竹
常见面试题:讲讲你对tcp的三次握手的理解吧,为什么它需要三次?
tcp在TCP/IP模型中属于第二层,是一个传输层协议,主要用于保证可靠性和流量控制维护的某些状态信息,像Socket、序列号和窗口大小等,【握手】其实就是连接; 那么其实就是两点,
- 连接干了什么,怎么做到的? 2. 为什么连接要三次? 我们都知道tcp是基于【确定应答】机制的,每个数据报都有一个序列号,存在报头的16进制数字seq字段中。
-
关于第一点,连接主要做了两件事:时期区分、唯一起点;时期区分是指在连接过程中自然数据报不会携带真正的传输数据,而又要保证告知双方,对方的第一个传输数据报序列号是多少,所以为了区分连接初始化时期和连接传输时期,TCP在握手期间将数据报的报头【控制标识】中的SYN设置为1,此时的数据报序列号在RPC 793里叫作ISN,其实也是seq字段,换个叫法而已。而真正的第一个传输数据报序列号是对方返回回来的ACK字段,一般是ISN+1;这样,我们就达到了双方得知传输序列号起点的目的。
-
第二点,为什么要三次,我之前的理解主要是三个方面:
-
三次握手才可以阻止重复历史连接的初始化(主要原因)
-
三次握手才可以同步双方的初始序列号
-
三次握手才可以避免资源浪费
第一点其实在RFC 793 也就是TCP的协议RFC里明确说过,但没说原因,但很容易理解的是,网络的不确定性导致有可能出现【SYN连接请求被堵塞,发送端重发后,结果旧SYN又到达了目标端】的情况,在第二次连接服务端响应SYN和ACK后,客户端就能够依据自己的信息得知这个连接是历史连接,从而在第三次连接时设置报文头中的rst字段发送给服务端以重置连接,而如果是两次握手就没办法阻止历史连接的初始化了。 第一点是主要原因,而第二点很好理解,C端告知S端自己的ISN后,S端自然要回应一个【确定收到】,以及发送自己的ISN给C端;而C端收到后自然也要告诉S端以及收到了对方的ISN,这必然需要三次通信。 第三点的话,反证法,如果是两次握手,当客户端SYN请求被堵塞,导致客户端重发了大量的请求连接时,服务端收到后。由于服务端不清楚客户端是否收到了自己的ACK确定信号,所以只好对所有的SYN建立连接,这些冗余的连接自然造成了资源浪费
-