序言
在前一篇文章中,我们介绍了 UDP协议
(点击查看)👈,该协议给我们的感觉就两个字 — 简单,只是将我们的数据进行简单的添加报头然后发送。当然使用起来虽然简单,但是否能送到目的地,那就要看网络的状态了以及一些随机成分了。
今天介绍的 TCP 比起前者的原理要复杂很多,但是正是因为这个缜密的设计才能让我们的通信更加的稳定!
TCP 协议段格
光是看该协议段格式的设计就比 UDP 丰富许多:
字段很多,我们在这里只是简单的介绍不过多的阐述,在后面讲述功能时会将对应的字段进行详细的介绍。
头两个字段,源端口,目的端口 也就是表示数据是从哪个进程来,到哪个进程去,这在 UDP 中出现过。现在介绍我们之前没见过的:
-
序列号:TCP 会将数据较大的数据包进行分段处理后再发送,序号
用于标识数据段的顺序,确保数据按顺序到达接收方
。 -
确认应答号:用于确认已接收到的数据,确保数据的可靠传输。确认号的值表示接收方期望收到的下一个数据段的序号。
-
首部长度:表示 TCP 报头的长度,单位是 4 字节。这代表 TCP 长度的最大值为 60 字节。
-
窗口大小:通过滑动窗口来控制流量,后面会详细介绍。
-
6 个标志位:
–URG
: 紧急指针是否有效
–ACK
: 应答标记位
–PSH
: 提示接收端应用程序立刻从TCP
缓冲区把数据读走
–RST
: 对方要求重新建立连接
–SYN
: 请求建立连接
–FIN
: 断开连接 -
校验和,紧急指针,选项:本篇文章不提及。
确认应答机制
1. 基本原理
灵感来源于生活!举个简单的例子:你正在厨房里做饭,你的孩子正在房间里愉快的开黑。饭做好了,你朝着房间说:吃饭了!吃饭了!你的孩子收到了,说了一声:好的!我来啦!但是如果房间里没有任何的回应呢?那你肯定再次重复,直到你的孩子听见了,对你有了答复。
所以怎么保证对方收到了你的消息呢?给你应答!确认应答机制就是这样:当接收方成功接收到发送方发送的数据段后,它会向发送方发送一个 ACK
报文,以确认该数据段已被成功接收。
2. 基本流程
发送方将要发送的数据 每个字节的数据都进行了编号.即为序列号
:
然后,发送方将这些数据段依次发送给接收方。
就比如,发送端现在一共将数据分成了三段,每一段的序列号分别是:1000,2000,3000。接收端收到序列号为 1000 的数据包时,会发送一个应答报文(ACK 置 1
),表示收到该段数据。那么,该确认应答号应该是 1000 咯。
不是的!确认应答号 = 接受的序列号 + 1,表示 收到该确认序列号之前的数据,期望的收到的下一个数据段的开始。
为什么要这样设计呢?允许少量的应答报文丢失。如果我们使用的第一种方式,那么我们必须保证收到该序列号报文对应的 ACK
报文,没收到就认为是丢失,需要补发!但是我们采取第二种方式,比如现在一共发了六个数据包,就算前面的丢失了,但是只要返回了第六个的应答报文,那么就意味着六个都到了!
重传机制
在网络中传输数据,免不了会遇到数据丢失的情况,那么 TCP 遭遇这种情况时,是怎么处理的呢?
1. 超时重传
主机 A 向 主机B 发送了一段数据,但是 A 迟迟没有接收到来自 B 的应答报文。会是什么原因呢?
- 主机 A 的发送的数据包丢失了或阻塞了
- 主机 B 的应答报文丢失了或阻塞了
主机 A 肯定不知道原因是什么,他只会补发数据直到收到应答报文!那么什么时候 A 会去补发数据呢:
首先这个时间间隔肯定不是固定的!因为网络情况是波动的,当网络延迟高的时候,时间肯定短一点,及时的去补发数据;当网络延迟低的时候,时间肯定长一点,避免频繁发送重复的包。
具体的步骤是,起始的时间间隔为 500ms
,如果重发一次之后,仍然得不到应答,等待 2*500ms
后再进行重传。如果仍然得不到应答, 等待 4*500ms
进行重传。类推,累计到一定的重传次数,TCP
认为网络或者对端主机出现异常,强制关闭连接。
2. 快重传
比如现在我们一共有 10 个数据段,序号分别是 1000,2000,… ,10000, 现在发送端将数据全部发送,除了 2000 都到了接收端,那么接收端是如何返回应答报文的呢:
为什么返回的应答报文的确认应答号都是 1001 呢?在之前说了,确认应答号代表该号数之前的数据已经被接受!
就拿序列号为 5000 的来说,该序列号对应的报文确实已经被收到了,但是他之前的报文还缺一个 2000(1001 ~ 2000),所以只能是返回 1001。
当发送端接收到 连续的3个重复确认
,它会立即重新发送丢失的数据段,而不需要等待超时时间到期。
TCP 的确认应答机制通过发送 ACK报文
、引入重传机制和序列号管理等方式,确保了数据的可靠传输。这一机制在 TCP 协议中起着至关重要的作用,为网络通信的可靠性和稳定性提供了有力保障。
连接管理
在这里的连接管理,实际上就是我们常听到的三次握手建立连接,四次挥手断开连接的过程。
1. 三次握手
首先,我们来分析一下三次握手的过程:
- 客户端发送
SYN
报文,想要和服务端建立连接,进入SYN_SENT
状态 - 服务端接收到请求后表示同意,并且也表示想要建立连接,进入
SYN_RCVD
状态 - 客户端收到报文后,回应最后一个
ACK
报文,进入ESTABLISHED
状态 - 服务器收到应答后,也进入
ESTABLISHED
状态
在这里我们首先解决几个前面没有介绍的知识点:
- 标记位报文:在这里我们提到发送
SYN报文
(表示建立连接),但是SYN
只是一个标记位呀?没关系的,我们只需要将该标记位置 1
,数据字段不填就是了。常见的还有:ACK报文
(表示应答对应的数据),FIN(请求断开连接)
…- 捎带标记位:比如当 A 向主机 B 发送数据后,B 肯定需要发送应答报文,但是此时恰巧 B 也有向 A 发送的数据,那么就可以将该数据和应答合并发送(两者并不会冲突,因为应答只需要将标记位
ACK
置 1 即可),不需要单独发送一个仅包含
ACK
的包,从而减少网络上的数据包数量
好的了解三次握手建立连接的步骤后,我们提出以下问题:
- 为什么是三次,不是四次,五次?
- 如果第一次握手请求丢失会怎么样?
- 如果第二次握手请求丢失会怎么样?
- 如果第三次握手请求丢失会怎么样?
问题只会帮助我们更好的理解知识,现在我们一个一个的来解释一下:
问题一:
保证双方具有发送和接受信息的能力
:在这三次握手中,发送端发送了一次数据(最开始的SYN报文
),接受了一次数据(ACK + SYN
);接收端接受了一次数据(最开始的SYN报文
),发送了一次数据(ACK + SYN
)。三次握手以最小的成本保证双方具有接收和发送的能力! 你四次(如果将中间的捎带应答分开,分两次发,也就是四次应答!)五次都行,但是没必要!- 初始化并同步序列号:接收端和发送端根据序列号来标识每个传输的数据包,确保数据的顺序性和完整性。你想一下,如果你在第一次发了一个数据包但是在网络上阻塞了,数据迟迟没到,你断开了连接;过了一会你重新建立连接,再次发送数据,好巧不巧第一次在网络上阻塞的数据也到了接收端,如果接收端接受了那么肯定数据就错乱了!为了避免这种情况,
每次建立连接时,双方都会生成一个新的初始序列号,这是为了确保通信的可靠性和安全性
。 - 避免历史连接:在这里我们引入一个实际的场景:
原来存在一个历史的连接请求阻塞在网络中,现在客户端发起一个新的连接请求,但是历史的请求也到达了服务端。如果没有中间那次服务端的确认,那么在两次握手后服务端就成功进入ESTABLISHED
状态。如果服务端向客户端发送相应的数据,这不是妥妥浪费资源吗?所以,我们需要一个中间状态来确认对方的身份,阻止历史连接造成资源的浪费!
问题二:
如果第一次链接请求都丢失了,那么则会触发前面说到的 超时重传
。
问题三:
- 对于客户端来说,我没有收到你的
ACK
,那么是不是我的请求没有送到呢?于是会触发超时重传
- 对于服务端来说,我的信息都发出这么久了咋还不回我,触发
超时重传
两者都会触发 超时重传
,因为在网络上确认对方收到自己信息的唯一方式就是收到相应的应答,没收到应答,我就认为你没有收到我的信息!
问题四:
因为这个第三次握手的 ACK
是对第二次握手的 SYN
的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK
报文,直到收到第三次握手或者达到最大重传次数。
这就是三次握手建立连接的过程。
2. 四次挥手
老样子,先认识到四次挥手的过程是什么样:
- 客户端发送
FIN
报文请求断开连接,进入FIN_WAIT_1
状态 - 服务端收到请求,返回
ACK
应答报文,进入CLOSED_WAIT
状态,客户端进入FIN_WAIT_2
状态 - 服务端发送
FIN
报文请求断开连接,进入LAST_ACK
状态,客户端进入TIME_WAIT
状态 - 服务端收到
ACK
后断开,客户端一段时间后断开
了解到四次挥手的过程后,我们提出以下问题:
- 为什么是四次挥手?为什么不在第一次时就直接断开?
- 为什么客户端在最后需要等待一段时间?
问题一:
引入生活场景。你和你的女朋友(or 男朋友)吵架了,你的女朋友说,我不想聊了,我要断开连接!好的,现在她不说话了,你有两个选择:
- A:不聊就不聊,我也断开连接!
- B:向她解释,千万不能断开连接!
通常情况下,咋们还是选择第二种。你的女朋友确实不会再发送消息给你了,但是不代表她不能听呀!她的话说完了,你的话也许还有呢?
现在回到网络上,客户端断开代表他的数据已经发送完了但是还能接收消息,而服务端可能还存在着需要处理的数据需要发送给客户端。当服务端的数据也已经处理完了,就会发送 FIN
报文同意断开连接。
所以说断开连接需要双方的同意,需要四次挥手!那有没有可能,服务端确实没有什么数据需要处理的情况呢?那肯定有,所以此时我们中间的 ACK
和 FIN
就可以捎带应答的方式发送,四次挥手变成三次回收!
问题二:
保证被动的一方正常的断开
:在这里不是主动断开的一方统称为被动的一方。四次挥手的最后客户端向服务端发送一个ACK
应答报文表示收到了,服务端这才关闭连接。但是如果该报文没有正常送达呢?那服务器肯定要补发FIN
数据包嘛,如果此时客户端早就断线了,在收到服务端重传的FIN
报文后,就会回RST
报文,这并不是一个好的结束方式。处理历史遗留数据
:在这里大家记住一个结论:序列号和初始化序列号并不是无限递增的,无法根据序列号来判断新老数据。会发生回绕为初始值的情况。
所以为了避免本次连接在网络中被延迟的数据被下一次新的连接所接收,所以我们需要一个时间来接受这些数据并丢弃!MSL 是报文在网络中的最大生存时间,2MAL 足以处理这些被延迟的报文了。
这就是四次挥手断开连接的过程。
总结
在这一篇文章中,我们介绍了 TCP 的协议段格式,以及保证其可靠传输的确认应答机制,重传机制,着重介绍了三次握手建立连接,四次挥手断开连接的过程,希望大家有所收获!