🎬 个人主页:谁在夜里看海.
⛰️ 道阻且长,行则将至
目录
📚引言
在之前的一篇文章中,我们详细介绍了 socket网络编程(基于 TCP 和 UDP),并且在应用层,我们已经能够熟练地调用API来编写简单的服务器和客户端应用。然而,要更好地理解网络编程,我们必须深入了解这两种协议的传输机制和它们的工作原理。今天,我们将通过对 TCP 和 UDP协议的进一步剖析,探索它们在实际传输中的差异、优势和局限性。
🔗文章链接:《【Linux-网络】初识计算机网络 & Socket套接字 & TCP/UDP协议(包含Socket编程实战)》
📚一、UDP协议
📖 1.概述
UDP(User Datagram Protocol,用户数据报协议)是面向报文的协议,即数据是通过报文的形式进行传输,类似于寄信。与 TCP 协议相比,它更加简洁和高效,体现在更少的报文段信息以及没有TCP协议的各种机制。
下面是UDP报文段的标准格式:
UDP数据报由 头部 和 数据部分 两部分组成:
UDP头部(8字节):
① 源端口号(16位):表示发送数据的应用端口。
② 目的端口号(16位):表示接收数据的应用端口。
③ 长度(16位):表示整个UDP报文的长度,包括头部和数据部分的总长度。最大值为64k字节。
④ 校验和(16位):用于检测传输过程中的数据完整性。虽然UDP是可选的,但如果IPv6被使用,校验和是必须的。
UDP数据部分:包括从源应用传来的数据,数据部分的大小由上面提到的 长度字段 决定。
⚠️ 由于UDP数据包的最大长度为64k,当要数据大于这个长度时,就需要进行分包传输。
📖 2.特点
与 UDP 的简洁性相对应地,UDP是 无连接的、不可靠的。
1️⃣ 面向报文:UDP是面向报文的协议,意味着每次发送的数据包都是一个独立的单位。
2️⃣ 较小的头部开销:UDP头部仅包含 8个字节,相比TCP的20字节头部,UDP的头部开销更小。
3️⃣ 不可靠性:UDP不保证数据的可靠传输,也不保证数据的顺序。如果数据丢失,发送方没有重传机制,接收方无法知道数据丢失。
由于UDP协议的 低延迟 和 简洁性,它特别适合用于对实时性要求高、但对数据可靠性要求较低的场景,例如实时视频通话、在线游戏等。
UDP协议提供的是一种不可靠的数据传输方式,适用于对实时性要求高的场景。然而,当我们需要确保数据包的可靠传输,避免丢包时,TCP协议则成为更合适的选择。
📚二、TCP协议
📖 1.概述
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接、可靠的传输层协议。它确保数据能够准确、按顺序地传输到目的地。为了保证数据传输的可靠性和稳定性,TCP报文段就需要包含更多信息。
下面是TCP报文段的标准格式:
其中序列号、确认号、标志位(ACK、SYN等)、窗口大小这些数据段需要结合TCP的特殊传输机制来理解,这里我们先区分一下UDP的报文长度和TCP的首部长度:
❓前者表示整个报文段的长度,后者仅表示报文头的长度,这是为什么呢?
✅UDP中报文长度这一数据段的作用是使接收方可以正确提取数据,而在TCP中这一职责由序列号+首部长度共同完成,其中序列号表示的是数据区的长度。之所以要标明报头长度是因为,TCP报头是不定长的,除了固定数据段之外,还有可选数据段。
TCP的选项数据段是TCP头部的可选字段,允许在连接建立后对TCP连接进行进一步的配置和优化。选项字段用于扩展TCP协议的功能,提供额外的控制信息,以适应不同的应用需求。
下面我们来一一介绍TCP的各种机制。
📖 2.机制
🔖确认应答(ACK)
我们提到,TCP协议提供的是可靠传输,这当中非常重要的一点是,要避免数据丢包问题,由于网络是不稳定的,数据丢包是很正常的现象。那么发送方怎么知道发送的数据有没有丢失呢?很简单,让接收方接受数据后回应一下,表示数据收到了,这就是TCP的确认应答机制。
但是当发送方发送多个数据包后,怎么知道收到的应答是对应哪个数据包的呢,这就需要TCP中的确认号和序列号字段了。
序列号:每个发送的数据段都被赋予一个唯一的序列号,用于标识数据流中的每一个字节。这个序列号是TCP可靠传输的基础,接收方根据序列号判断数据是否丢失或者是否按正确顺序到达。
确认号:接收方使用确认号来告诉发送方,自己已经成功接收到的数据的最后一个字节的序列号。确认号是接收方期望接收的下一个字节的序列号。
每一个ACK都带有对应的确认序列号,表示已接收了哪些数据,下一个数据应该从哪里开始。
🔖超时重传
基于确认应答机制,当发送方过了一段时间后,还没有收到来自接收方的确认应答,就可以判定数据包丢失了,此时就需要重新发送数据包,这就是超时重传机制。
这里的丢包分为两种情况:
① 发送的数据包本身丢失了;② 接收方的确认应答丢失了。
无论是哪种情况,都需要重新发送数据包,而对于第二种情况,接收方会收到两个相同的数据包,这个时候根据序列号判断,如果序列号相同,则认为两个数据包相同,会将重复的数据包丢弃,并不影响。
❓这个超时的时间该如何确定呢?
数据包传输到达的时间是会受网络环境影响变化的,如果超时时间设置的太长,会影响重传的效率;如果超时时间设置的过短,则有可能会频繁发送重复的数据包。
✅TCP为了保证在任何环境下都能比较高性能的通信,会动态计算这个超时时间。
以Linux为例,超时时间以500ms为单位,如果重传一次还没有得到应答,则等待2*500ms后再进行重传;之后还得不到应到,则等待4*500ms......直到累计一定的重传次数后,TCP判定网络或目标主机出现异常,强制关闭连接。
🔖连接管理
基于TCP协议进行通信的双方,一个基本的前提条件就是,双方都确保能正常发送和接受数据,这样才能保证通信的稳定与可靠性。所以在通信发生之前,需要对双方的发送、接受能力进行验证;同样地,在通信结束时,也需要确保双方都已关闭连接信道,避免单方面发送数据却不能被接收也等不到应答的情况。这就是三次握手与四次挥手机制。
在理解三次握手与四次挥手之前,我们需要了解一个预备知识,那就是TCP报文段的标志位。
TCP报文段中的标志位(也叫控制位)用于控制TCP连接的建立、维护和终止。每个TCP报文段中都有一个6位的标志字段,分别是:
① URG(紧急标志):用于指示数据段中有紧急数据需要处理
② ACK(确认标志):表示确认号字段有效,即接收方通过确认号来告知发送方它期望接收的下一个字节的序列号
③ PSH(推送标志):表示接收方应该立即将数据推送给应用层
④ RST(重置标志):强制断开TCP连接,用于一方出现错误或不可恢复的异常的情况
⑤ SYN(同步标志):用于连接的建立,表示请求连接或响应连接请求
⑥ FIN(终止标志):用于连接的断开,表示发送方没有数据要发送,并请求关闭连接
在TCP三次握手过程中,SYN标志位会在第一次和第二次握手时被使用:
1️⃣ 客户端发起连接时,会发送一个带SYN标志的报文,表示请求建立连接。
2️⃣ 服务器响应客户端请求时,也会发送一个带SYN标志的报文来表示同意连接。
在TCP四次挥手过程中,FIN标志位被用来发起和响应连接的终止:
1️⃣ 主动关闭连接的一方会发送带有FIN标志的报文,表示它已经完成数据传输并希望关闭连接。
2️⃣ 被动关闭的一方也会发送带有FIN标志的报文作为确认,表示它同意终止连接。
下面是三次握手的全过程:
① 第一次握手:通信发起方向接收方发送SYN报文,请求连接(打开写端,表示我要写数据了)
② 第二次握手:接收方收到SYN报文,回复ACK报文,表示同意连接请求(打开读端,表示我能收到数据);并同时也发送SYN报文(打开写端,我也要写数据了)
③ 第三次握手;发起方收到SYN报文,同意回复ACK报文(打开读端,表示我也能收到数据)
四次挥手的全过程:
① 第一次挥手:断开连接发起方向接收方发送FIN报文,表示希望关闭连接(关闭写端,表示不再写数据了)
② 第二次挥手:接收方收到FIN请求,回复ACK表示同意(关闭读端,我不再等待接收数据了)
③ 第三次挥手:接收方发送FIN请求(关闭写端,我也不写数据了)
④ 第四次挥手:发起方收到FIN请求,回复ACK表示同意(关闭读端,我也不等待接收数据了)
从上面的步骤来看,其实第二次握手是包含两步的:接收方打开读端与写端,这与第二次挥手、第三次挥手是对应上的,问题来了:
❓① 为什么要把接收方打开读端与写端的操作合并为一步;② 为什么接收方断开读端和写端的操作不能合并成一步。
✅先回答第一个问题,我们要知道,每一个标志位的独立发送都需要独立的报文段,接收方要分别发送ACK与SYN的话就需要占用两个报文段,既然如此,为什么不把这两步合成一步,节省资源呢,于是就有了第二次握手。
✅对于第二个问题,断开连接发起方发送FIN报文的意思是,不会再发送数据了,也就是关闭了写端,但这并不意味着读端也关闭了;同样地,接收方在收到FIN报文后回复ACK的意思是,关闭读端,但也不意味着写端就要一并关闭,相反地,它依旧可以发送数据,对方也依旧可以收到数据,因此二、三挥手要分开来执行。
在TCP协议中,连接的建立与断开遵循严格的状态变化流程。理解连接发起方、接收方以及断开发起方、接收方的状态变化对理解TCP的三次握手(连接建立)和四次挥手(连接终止)至关重要。
连接建立(TCP三次握手)
① 连接发起方(客户端)
初始状态:CLOSED(连接关闭);
发送SYN请求:客户端进入 SYN_SENT 状态,发送一个SYN报文;
接收ACK响应: 确认连接建立,进入 ESTABLISHED 状态,开始数据的双向传输。
② 连接接收方(服务器)
初始状态:LISTEN(监听状态)
接收SYN请求: 进入 SYN_RCVD 状态,并发送一个带有SYN和ACK标志的响应报文(SYN-ACK),表示同意建立连接。
等待确认: 收到客户端的确认ACK报文后,连接成功建立,进入 ESTABLISHED 状态,开始数据的双向传输。
三次握手过程状态变化图
- 客户端(发起方):
CLOSED
→SYN_SENT
→ESTABLISHED
- 服务器(接收方):
LISTEN
→SYN_RCVD
→ESTABLISHED
连接断开(TCP四次挥手)
① 连接断开发起方(客户端/服务器)
1. 初始状态:ESTABLISHED(已建立连接);
2. 发送FIN请求:此时,该端点的写端被关闭,进入 FIN_WAIT_1 状态;
3. 等待ACK响应: 主动关闭方在 FIN_WAIT_1 状态等待接收方的ACK报文确认其断开请
4. 等待接收方的FIN: 此时,主动关闭方进入 TIME_WAIT 状态,等待足够的时间确保接收方收到了最后的ACK报文。
② 连接断开接收方(服务器/客户端)
1. 初始状态:ESTABLISHED(已建立连接);
2. 接收FIN请求: 被动关闭的一方在 ESTABLISHED 状态收到主动关闭方的FIN报文后,进入 CLOSE_WAIT 状态,表示确认收到对方的断开请求,并准备关闭自己的写端。
3. 发送ACK响应: 被动关闭方发送ACK报文,确认收到对方的FIN报文。此时,它的写端被关闭,但接收端仍然保持开放,允许接收剩余的数据。
4. 发送FIN: 当被动关闭方完成数据接收后,主动发送带有FIN标志的报文,表示自己也没有数据要发送了,进入 LAST_ACK 状态。
5. 等待确认: 被动关闭方等待对方的ACK确认,确认连接完全断开后,进入 CLOSED 状态。
四次挥手过程状态变化图
- 主动关闭方:
ESTABLISHED
→FIN_WAIT_1
→FIN_WAIT_2
→TIME_WAIT
→CLOSED
- 被动关闭方:
ESTABLISHED
→CLOSE_WAIT
→LAST_ACK
→CLOSED
🔖滑动窗口
基于确认应答机制,如果每发送一次报文,都需要等待应答,收到ACK后再发送下一个报文,这样做会大大影响性能。于是我们可以一次性发送多个报文,并且同时等待多个应答,因为发送的报文是基于序列号顺序发送的,所以可以看作一个滑动窗口,窗口内部是已发送但还未收到应答的报文,当收到应答后,右移窗口的左端;发送新报文后,右移动窗口右端。这就是滑动窗口机制。
❓如果在发送过程中出现丢包,该如何解决?这里分两种情况:
① 数据包已抵达,但ACK丢了。这种情况下,基于超时重传机制,发送方会重新发送数据包,直到接收到相应的ACK为止
② 数据包丢失了。例如,发送方一共发送了序列号为1~5000的数据包,但是当中1001~2000的数据包丢失了,其余的没有丢失。在这种情况下,接收方返回的确认序列号会一直为1001,表示1001开头的报文没有收到,此时发送方得知后会重新发送1001~2000的数据包,此时接收方返回的确认序列号为5001(因为2001~5000的数据包已经收到了)。
这种机制被称为“高并发重传机制”。
🔖流量控制
接收方收到数据包后时并不会立即处理,而是暂存在接收缓冲区中,而缓冲区的空间时有限的,这就意味着如果发送方一次性发送过多数据包,就会出现由于缓冲区空间不足而丢包的情况。 为了避免这种情况的发送,发送方就需要控制发送速度,就需要借助流量控制机制。
控制速度的依据是接收方的缓冲区剩余空间大小,而发送方该怎么得知这一信息呢,就需要借助TCP报文段中的“窗口大小”字段。
接收方依据缓冲区剩余空间,设置窗口大小并通过ACK应答报文告知发送方,如果窗口大小变小,发送方就会减缓发送速度;如果窗口大小为0,发送方就停止发送数据,但是会时不时发送窗口探测,用于探测请求接收方窗口更新。
❓即使发送方不进行窗口探测,接受方在缓冲区有空间剩余之后还是会发送窗口更新报文,但是为什么发送方还是需要时不时进行探测呢?
✅因为接收方的窗口更新报文有可能丢失,如果发送方不探测,接收方就不会重传
🔖拥塞控制
基于流量控制机制,发送方在数据发送的过程中可以很好地控制速度,但是还存在一个问题,那就是在刚建立通信连接,第一次发送数据时,并不知道接收方的缓冲区情况,此时并不能确定该发送多少数据。于是TCP引入慢启动机制,即一开始只发送少量数据,目的是探测对方的“吞吐量”大小,清楚情况后,再决定用什么速度传输数据。
那么这个传输速度具体该如何确定呢?这里要引入“拥塞窗口”的概念:
数据发送开始时,拥塞窗口为1;此后每收到一个ACK,拥塞窗口加1,并取拥塞窗口与ACK中窗口大小的较小值作为本次发送数据的大小。
如此一来,数据发送速率是呈指数级增长的,但是这到了后面,速度会变得很不可控,并且很大概率会出现丢包(发送速度过快),因此要对速度进行限制,就需要借助“慢启动阈值”这一概念。
当拥塞窗口超过阈值时,就不再按指数级增长,而是按线性增长。这个阈值初始为窗口最大值,之后每发送一次超时重传,阈值变为原来的一半,并且拥塞窗口置为1(将阈值慢慢逼近一个合理值,既保证了传输效率,又减少了出现丢包的可能性)
🔖延迟、捎带应答
接收方在ACK应答的同时,也在处理缓冲区的数据,也就是说,如果ACK立即应答,其时效性会比较低下(剩余空间远大于窗口值,因为一部分数据已被处理),于是TCP引入了延迟应答机制。接收方在接收数据之后,会等待一段时间再发送ACK应答,这样在一定程度上可以确保窗口大小的时效性。
基于延迟应答,接收方的数据包发送和ACK应答可以共用一个数据段,这样可以大大节约资源,第二次握手就是很好的例子。
📖3.总结
TCP协议通过以上多种机制确保了数据传输的可靠性,并且通过一系列优化手段提升传输性能。因此,以上机制可以大致分为两类:可靠性机制和性能优化机制。
可靠性机制 | 性能优化机制 |
校验和 | 滑动窗口 |
序列号(按序到达) | 快速重传 |
确认应答 | 延迟应答 |
超时重发 | 捎带应答 |
连接管理 | |
流量控制 | |
拥塞控制 |
以上就是【深入拆解TCP核心机制与UDP的无状态设计】的全部内容,欢迎指正~
码文不易,还请多多关注支持,这是我持续创作的最大动力!