在之前的文章中,我们学习了 HTTP 和 HTTPS 协议。但无论是 HTTP 还是 HTTPS,它们最终都要依赖传输层来真正传输数据。传输层有两个最重要的协议:UDP 和 TCP。一个追求极致的速度,一个追求绝对的可靠。本文就将带你深入理解这两大协议的核心机制,以及它们各自适用的场景
一. UDP 协议
在前面的网络编程一文中,我们已经简单提到了 UDP 协议的几个特性:无连接、不可靠传输、面向数据报 和 全双工,接下来,我们来进一步的了解 UDP 协议
1. UDP 协议的 报文格式
UDP 的报文格式可以如下图所示

其中,我们对 16位源端口号 和 16位源目的端口号 还是比较了解的,16位源端口号:客户端/服务器 上的哪个程序发出去的数据包,16位目的端口号:数据报要交给 客户端/服务器 上的哪个程序
1.1 16位UDP长度
这个 16位UDP长度 指的是整个 UDP数据报的长度(报头 + 载荷),这个长度有 2个字节来表示,即表示范围为 0 ~ 65535(64kb)
64kb 放到现在来看的话是一个非常小的数字,毕竟现在随便一张图片就有好几个mb了,所以,UDP 数据报的长度是不足以满足现在的需求的了。那么 ,我们要如何传输一个比较大的数据呢?有以下两种方案
1. 在应用层代码中做拆包操作,将一个大的应用层数据包拆成多个小的应用层数据包,再通过使用多个UPD数据报进行传输(比较麻烦,工作量比较大)
2.直接使用 TCP协议进行传输,因为 TCP协议是面向字节流的,没有数据包长度的限制
1.2 校验和
校验和是验证数据是否发生修改的手段。在上文的 HTTPS 协议中我们也提到过校验和的概念,HTTPS 协议 的数字签名的校验和主要是为了防止黑客篡改数据,而 UDP 协议的校验和则是为了防止数据在传输过程中发生的 “ 比特翻转 ” (1 - > 0 , 0 - > 1),而不是为了防止他人篡改
使用校验和验证数据是否发生修改的大致过程如下;
在 UDP 数据报发送之前先将整个数据报的数据都代入计算出一个校验和,并将数据和校验和一起发送给对端。接收方在接收到数据报之后会重新计算一遍校验和来和收到的校验和进行对比,如果发现两次的校验和不一致,即数据发生了 “ 比特翻转 ”,就会直接丢弃该数据报
在 UDP 中使用 CRC 的方式来对校验和进行校验(循环冗余校验):将每个字节(除了校验和的那部分)当作整数进行累加,累加得到的结果就是 CRC 校验和
二. TCP 协议
在前面的网络编程一文中,我们已经简单提到了 TCP 协议的几个特性:有连接、可靠传输、面向字节流 和 全双工,接下来,我们来进一步的了解 TCP 协议
1.TCP 协议的报文格式

其中,和UDP协议一样,我们对 16位源端口号 和 16位源目的端口号 还是比较了解的,16位源端口号:客户端/服务器 上的哪个程序发出去的数据包,16位目的端口号:数据报要交给 客户端/服务器 上的哪个程序
1.1 4位首部长度
由于选项的存在,这就使得 TCP 的报头长度是可变的,我们就需要 4位首部长度 来表示 TCP的报头长度(描述的就是TCP 头部自身的总字节长度,也就是 TCP 报文里除去后面应用载荷数据的那一段头部长度),由 4个bit位表示,但由于其使用 4个字节 为单位,所以它的表示范围为 0 ~ 60字节
1.2 保留位(6位)
这就是 TCP 设置的巧妙之处,我们知道,UDP 的最主要的问题就是它所表示的长度不够但又不能进行扩展,TCP 的设计者就考虑到了这样的问题,在 TCP的报头中预留了一些 “ 保留位 ”,这些保留位现在用不上,但在之后可能会派上用场
1.3 标志位
下图就是 TCP 最核心的 6 个标志位,在下文中的 TCP 的核心机制的讲解中会一一给大家讲解

2. TCP 的核心机制
2.1 确认应答
保证 TCP 可靠性的一个重要前提,这里的可靠性并不是指 A 向 B 传输的数据 B 一定能收到,而是说 A 能够感知到 B 是否收到了数据,若 B 没有收到数据,就要进行对应的处理
确认应答:接收方收到数据后,必须给发送方回一条确认报文(ACK),告诉对方 “ 我收到了 ”,如果发送方在一定时间内没有收到接收方发来的 ACK 确认报文,那么发送方就会认为接收方并没有收到数据
在 TCP 引入了确认应答这个机制之后,似乎 A 就能完美感知到 B 是否真的收到某一条数据了,即使同时有多段 TCP 数据报的传输,也只需要按照时间顺序对应各个 B 返回的 ACK 报文就可以了。但是,事实真的是这样吗?说到这里,我们就不得不给大家介绍网络上一个比较神奇的现象 -- 先发后至
先发后至:发送顺序靠前的数据包,反而比后发的数据包更晚到达接收端,出现乱序的现象
原因:网络路由是不固定的,不同数据包走的线路、交换机拥堵程度也是不一样的,这样就不能保证先发出的数据报比后发出的数据报更快到达接收方
TCP 解决先发后至问题的方法 :给传输的数据进行编号,后续接收方根据编号对收到的数据报进行排序
为了对 TCP数据报 进行编号,我们又不得不提到上述 TCP报文格式的两个字段 —— 32位序号 和 32位确认信号

我们知道,TCP 是面向字节流的,所以其在对数据进行编号时,并不是按照第一条数据报,第二条数据报这种格式进行,而是按照 “ 字节 ” 来编号的(每个字节都会分配一个编号,编号之间是连续递增的)
32位序号:给发送出去的数据报进行的编号(填写的是要发送的数据报的载荷部分的第一个字节的序号)
作用:接收方通过这 32位序号 对所收到的数据报进行重新排序,解决网络中的先发后至问题
32位确认序号:接收方告诉发送方它已经收到的数据报,并说明下一次要哪一号(填写的是收到的数据报的载荷部分的 最后一个字节的序号 + 1 )
作用:告诉发送方哪些数据报已经确认收到 并 告知发送方下一个要发送的数据报
确认报文(ACK)中的 ACK 标志位的值为 1,表明这是 确认报文

2.2 超时重传
超时重传是针对 TCP 传输发生丢包后的处理方式。
丢包:数据包在网络传输中途,因为线路拥堵、设备故障、缓冲区满等原因,没能到达接收方,直接消失了,就叫丢包。丢包是一种不可避免的客观现象
TCP 中引入了一个超时时间(判定超时的时间阈值,不是固定数值而是动态变化的),在发送方在发出数据包后就会开始计时,当发送方在超出超时时间后仍不能接收到接收方返回的确认报文ACK时,那么发送方就会认为发生了 “ 丢包 ”,并重新向接收方发送一次这个数据包。随着超时的次数增多,发送方等待的超时时间也会适当的进行延长(并不是无限增长),当超时次数达到一定的次数时,发送方就会认为网络已经发生了严重的故障,此时就算再继续重传也没什么意义,所以就会放弃这一次传输
触发超时重传的两种可能
1. A 向 B 发送的数据包丢失
在 A 向 B 发送数据包时发生了丢包,A 就不能在超时时间内收到 B 返回的 ACK 确认报文,在超出超时时间后 A 就会重新向 B 发送这个丢失的数据包
示意图如下所示:

2. B 向 A 发送的 ACK 丢失
在 A 向 B 发送数据包时 B 成功地接收到了该数据包,但在 B 向 A 返回 ACK 确认报文时却发生了丢包,那么 A 还是不能在超时时间内收到 B 返回的 ACK 确认报文,在超出超时时间后 A 还是会重新向 B 发送这个数据包
在这种情况下,B 会收到很多重复的数据包。对此,TCP 中有一个 接收缓冲区,在往接收缓冲区中存储数据包时,就会先根据序号在接收缓冲区中查找一下,如果接收缓冲区中已经存在该数据包,就会直接丢弃重复的数据包;如果没有,才会将该数据包放入接收缓冲区中
示意图如下所示:

3.3 连接管理
连接管理分为 建立连接 和 断开连接 两种,接下来就分别给大家来介绍
3.3.1 建立连接 -- 三次握手
握手操作,并不涉及实际的业务,只是单纯地 “ 打个招呼 ”,让对端能够认识到自己
三次握手的作用:使 TCP 建立可靠连接,双方确认发送、接收功能都正常,并同步对端的初始序列号(双方各自保存一下对方的关键信息)
三次握手的流程:
1.第一次握手:客户端向服务器发送 同步报文 SYN,并带上自己的初始序列号,告诉服务器它要发起连接
2.第二次握手:服务器在接收到客户端发来的连接请求后,向客户端返回一个 同步 + 确认 报文(SYN + ACK),在确认收到客户端发来的序列号并告诉客户端下一个该发的序列号 的同时 同意进行连接(记录下了客户端的初始序列号),并返回自身的初始序列号
3.第三次握手:客户端在接收到服务器发来的初始序列号后向服务器返回一个确认报文 ACK,这时客户端也与服务器建立了连接(记录下了服务器的初始序列号)
三次握手完毕后,客户端和服务器均已记录下了对端的初始序列号,成功地建立起了TCP连接
同步报文(SYN)中的 SYN 标志位的值为 1,表明这是 同步报文
三次握手的流程草图如下图所示:

也许有的同学会有疑问,这个服务器在接收到客户端发送来的 SYN 报文之后返回的 ACK 和 SYN 报文为什么能合在一起发给客户端呢?对于这个问题,我们就要提到,在这里的 SYN 和 ACK 都是由系统内核来负责的,与用户的代码无关,所以能够保证这两个报文是一起发送给客户端的,所以能合并为一个报文一起发送(合并操作能够有效地提高数据传输的效率)
带有 客户端/服务器 连接状态变化 的三次握手的流程图如下所示

接下来,我们来分别介绍一下该图上的各个状态
CLOSED:不存在的状态,表示此时还没有连接
SYN_SENT:客户端主动发送 SYN 报文发起连接,等待服务器回复 SYN + ACK
SYN_ RCVD:服务器在接收到客户端发送来的 SYN 同步报文之后,返回 ACK + SYN 报文后等待客户端发送来最后一次 ACK (这个状态存在的时间特别短暂)
ESTABLISHED:客户端与服务器成功建立起 TCP连接,接下来就可以进行其他业务数据的发送与接收了
三次握手的意义
1. 三次握手相当于 “ 投石问路 ”,可以先初步的探测一下网络的通信链路是否通畅,能否正常地接收数据(网络通畅是可靠传输的重要前提)
2. 验证通信双方的发送能力和接收能力是否正常
第一次握手:没有证明的功能
第二次握手:确认客户端的发送能力、服务器的接收能力正常
第三次握手:确认客户端的接收能力、服务器的发送能力正常
3.在三次握手的过程中可以协商一些关键信息
其中,有一个比较典型的关键信息就是在本次通信过程中,TCP 数据报的序号要从多少开始(初始序号一般不为0),这样可以使服务器在接收到上一次连接的数据报后(上一次连接中的数据报可能迟迟不能到达服务器,直到服务器都建立了下一个连接了才抵达)能够快速的进行丢弃,避免进行误读
3.3.2 断开连接 -- 四次挥手
断开连接就是指 客户端与服务器之间互相发送 FIN 报文,各自将对方的初始序列号删除
四次挥手的流程:
1. 第一次挥手:客户端向服务器发送 FIN 报文,告诉服务器我要断开连接了
2. 第二次挥手:服务器向客户端发送 ACK 报文,表示服务器已经确认收到客户端断开连接的请求,但服务器可能还要执行一些代码逻辑,不会同时发送 FIN 报文
3. 第三次挥手:服务器向客户端发送 FIN 报文,告诉客户端他也可以断开连接了
4. 第四次挥手:客户端向服务器发送 ACK 报文,表示客户端已经确认收到了服务器的断开连接请求,双方各自删除对方的初始序列号,连接断开
结束报文(FIN)中的 FIN 标志位的值为 1,表明这是 结束报文
四次挥手的流程草图如下图所示:

带有 客户端/服务器 连接状态变化 的三次握手的流程图如下所示

接下来,我们来分别介绍一下该图上的各个状态
FIN_WAIT_1:客户端向服务器发送 FIN 报文,等待 服务器的 ACK 确认报文
CLOSE_WAIT:服务器收到客户端的 FIN ,回复 ACK,并执行在发送 FIN 报文之前剩余的代码逻辑
FIN_WAIT_2:客户端收到服务器返回的 ACK,确认服务器收到关闭请求,等待服务器执行完剩余的代码逻辑并发送 FIN 报文
LAST_ACK:服务器执行完了剩余的代码逻辑并向客户端发送 FIN 报文,等待客户端发送的最后一个 ACK 报文
TIME_WAIT:客户端收到了服务器发来的 FIN 报文,并向服务器发送最后一个 ACK,在发送完后客户端并不会立马关闭,而是要等待 2MSL 的时间(网络上任何两个结点传输过程中消耗的最大时间),看服务器是否会超时重传 FIN,以防止最后这条 ACK 发生丢包
CLOSE:连接正式断开
其中,CLOSE_WAIT 在实际开发中应该是看不见的,因为在服务器感知到客户端断开连接之后,就应该尽快地去执行 close 操作,如果发现服务器这里存在大量的 CLOSE_WAIT 且存在时间较久,那么你的代码中就很有可能存在有关于 close 的 bug
其实,在实际运用中,不一定就是客户端先主动发起 FIN,也有服务器先主动发起 FIN 的情况,不过大部分情况下还是客户端先主动发起 FIN(在上述的连接状态变化中,客户端和服务器谁是主动发起 FIN 的一方,谁就会进入 TIME_WAIT 的状态,谁是被动发起 FIN 的一方,谁就会进入 CLOSE_WAIT 的状态)
3.4 滑动窗口
我们知道,TCP 为了保证可靠性传输,引入了 确认应答 和 超时重传 这两个核心机制,但这也付出了相应的代价——效率。而滑动窗口就是 TCP 用来尽可能地减少降低的效率的机制(无论如何减少,最后的效率还是比没引入 确认应答 和 超时重传 之前低的)
在没引入滑动窗口前,对于每一个发来的数据报,对端都会返回一个 ACK 确认报文,源端口只有在收到 ACK 后才会接着发送接下来的数据报,这样一问一答式的通信方式就会造成效率的降低,示例图如下所示

那么,我们应该如何去在 引入 确认应答 和 超时重传 的基础上尽可能地提高效率呢?我们可以想想,主机A 能不能一次性地发送多个数据报给主机B呢?这,也就是我们接下来要讲的滑动窗口
窗口 指的就是发送方在没收到 ACK 的情况下所能连续发送的最多报文数
引入滑动窗口后数据传输的形式如下图所示

在发送方收到接收方返回的 ACK 之后,窗口就会向后滑动相应的位置,并将下一段报文发送给接收方。
以上述发送方收到 确认序号为 1001 的确认报文之后为例
此时发送方早已将 序号为 1001 的报文发给了接收方,所以发送方会将 1001 的数据报从发送缓冲区清除,并将窗口向后移动一个位置。此时窗口的最后一个位置就变为 序号为 5001 的数据报,发送方就会将 5001 的数据报发送给接收方。这个过程形似一个窗口在移动,所以将该机制称为 “ 滑动窗口”

窗口越大,发送方能够一次性连续向接收方发送的数据报的个数就越多,数据传输的效率也就越高,但窗口并不能无限大,否则会影响到传输的可靠性
那么,移动窗口 可以使 发送方一次性向接收方发送大批量数据报,而如果在这个过程中发生了丢包的情况,又要怎么进行超时重传呢?以下,我们分为两种情况来考虑
1. 数据报已经抵达接收方,但接收方返回的 ACK 丢失
这种情况的示意图如下所示

在这种情况下,一部分的 ACK 丢失其实是不会产生什么影响的,因为 ACK 是覆盖式的,即只要发送方收到了后面的 ACK ,也就可以确认前面序号的数据已经被接收方接收到了(比如发送方收到了 确认序列号为 6001 的ACK,那么发送方就可以确认接收方已经成功接收到了 1 ~ 6000 的数据)
2. 数据报直接丢失
这种情况的示意图如下所示

如上图所示,当发送方 1001 - 2000 的数据丢失了之后,接收方返回的 ACK 确认报文的 确认序号就全都是 1001,告知发送方应该发送序号为 1001 的数据。当发送方连续收到三个同样的 ACK确认报文之后,就会意识到 1001 - 2000 的数据可能是在传输的过程中发生丢包了!就会重新向接收方传输 1001 - 2000 的数据。当接收方成功地收到了 1001 - 2000 的数据了之后,此时返回的 ACK 确认报文就会直接从 7001 开始(因为此时接收方已经收到了 2001 - 7000 的数据,就差 1001 - 2000,在成功收到 1001 - 2000 的数据之后,接下来就继续从 7001 之后接收就可以了,这就叫做快速重传,哪个数据丢失就只需重传哪个数据,无需再重传其他已经收到的数据)
我们知道,滑动窗口不能无限大,太大了可能会影响到可靠性,这是因为接收方的处理能力 和 传输链路的转发能力 是有上限的。对此,TCP 引入了 流量控制 和 拥塞控制 来分别限制滑动窗口的大小
3.5 流量控制
TCP 根据接收方处理数据的能力来决定发送方的发送速度(滑动窗口的大小),这个机制就叫做 流量控制
我们可以把接收缓冲区想象成一个水缸,发送方发送数据就是往里面加水,而应用程序从接收缓存区中读取数据就是放水,如果水缸里的水太多溢出也就相当于发送方发送的数据发生了丢包,所以,我们需要根据接收方处理数据的能力来限制发送方发送数据的速度

在接受方返回的 ACK 确认报文中,报头中的 “ 16位窗口大小 ” 字段 就表示了接收方缓冲区剩余的空间大小,发送方就会根据这个 字段的大小来重新设定 滑动窗口 的大小(滑动窗口的大小是动态变化的)

流量控制的示例图如下所示

窗口探测:当发送方发现窗口大小为 0 时,就会暂停发送数据(此时接收缓冲区已经满了,再发送数据就会发生丢包),并进行等待。发送方等待的时间如果超过超时重发的时间后还没有收到接收方返回的窗口更新通知,就会向接收方发送一个窗口探测包,主动触发接收方返回 ACK 并 告知现在的窗口大小,看是否可以接着传输数据了。如果可以,就接着进行数据传输;如果还是不行,就接着重复上述过程
3.6 拥塞控制
对于上文所讲的流量控制是根据接收方处理数据的能力来限制窗口的大小的,而我们接下来要讲的拥塞控制则是根据传输链路的转发能力进行限制的
然而,在实际的网络通信中,数据包在网络设备的转发是十分复杂的,并不好具体衡量某一个网络设备的转发能力,所以,我们就可以把整个通信链路视为一个整体,通过 “ 做实验 ” 的方式找到一个合适的窗口大小
做实验的方式寻找合适的窗口大小:刚开始先按照一个比较小的传输速度(比较小的窗口)进行数据的传输,如果没有发生丢包的情况,就逐渐增大传输的速度。当速度达到一定的大小时,如果出现丢包,就减小传输的速度,又不发生丢包了,就又增大传输的速度,又发生丢包了,就又减小传输的速度。。。如此循环往复,就可以使窗口大小在可以维持的最大传输速度处保持动态平衡
拥塞窗口控制传输速度的过程示例图如下所示

刚开始,拥塞窗口(传输速度)呈指数式增长,直到达到 ssthresh 值后变为函数式增长,如果发生了丢包(即图中的收到 3 个重复 ACK 执行快重传),就会减小拥塞窗口为新的 ssthresh值(收到 3 个重复 ACK 执行快重传时的 拥塞窗口值的一半),再进行新一轮函数式增长
图中还演示了 TCP Tahoe 版本的拥塞窗口的控制窗口大小的过程,其是在发生丢包后就直接将拥塞窗口重新设置为 1,并重复之前的流程,只不过是将新的 ssthresh值设为发生丢包时的拥塞窗口的一半而已
3.7 延时应答
在默认情况下,接收方在收到发送方发来的数据报的第一时间就会返回 ACK 确认报文,但是我们也可以通过延时返回 ACK 的方式来提高效率
延时应答一般有两种方案:
1. 数量限制:每隔 N 个数据包就应答一次 (不会因为缺少了部分 ACK 而影响到 TCP 的可靠性,因为 ACK 是覆盖式的,有了后面的 ACK 就已经可以确认前面的数据报都已经收到了)
2. 时间限制:超出最大延迟时间就应答一次(参数通常可以调整)
在实际场景中这两种方案是综合选择的,数据传输密集时就按第一个方案,数据传输稀疏时就按第二个方案
延时应答的示例图如下所示

像这样,接收方就可以减少返回 ACK 的个数(返回 ACK 也会占用一定的网络资源),从而提高了数据传输的效率
3.8 捎带应答
捎带应答是 TCP 基于 延时应答而引入的机制,可以使得服务器在返回业务资源的时候顺便将上次的 ACK 带回去
其中,最典型的例子就是我们之前提到的 “ 四次挥手 ” 了,其示例图如下所示

在之前讲解 “ 四次挥手 ” 时,我们提到,在挥手的过程中不能像 “ 三次握手 ” 一样将服务器返回 ACK 和 FIN 这两个数据包合在一起发送回客户端,这是因为其返回 ACK 和 FIN 的时机是不同的。但当 TCP 引入了 “ 延时应答 ” 后,这两个时机就有了重合的可能,我们就可以让返回 ACK 的时机稍微往后延迟了一些,直到返回 FIN 时,将 ACK 也带入到 FIN 中,将两个数据报合成一个发送给客户端,就能起到提高传输效率的作用
3.9 面向字节流
当我们创建一个 TCP 的 socket 对象时,就会同时在内核中创建一个发送缓冲区和一个接收缓冲区
我们知道,TCP 是面向字节流的,它只负责传输一串连续字节流,没有独立数据包边界,操作系统内核缓冲区会缓存多个数据包数据,发送 / 接收时会把多段数据合并,这也就带来了一个问题 —— 粘包问题(不知道从哪里到哪里是一个数据包)
粘包问题
首先,我们要知道,粘包问题粘的是 “ 应用层数据包 ”,其次,这个问题在 TCP 的层面上是无解的,我们要想解决粘包问题,需要站在应用层上去解决
解决粘包问题的关键在于明确包与包之间的边界,基于此,我们就可以有两个方案来解决粘包问题
解决粘包问题的两个方案:
1.明确包与包之间的分割符(包的结束标记),应用程序读到这个分割符时就可以认为已经读取完了一个包
2.记录数据包的长度,可以在数据包的头部用一个字段记录下这个数据包的长度,应用程序读取完对应长度的字节后就可以认为已经读取完一个包
在 HTTP中,这两种方案都有所体现:
1. 在 GET 请求中,如果数据报没有 body 部分,就使用空行作为结束标记
2.在 POST 请求中,如果数据报有 body 部分,就使用 Cotent-Length 来记录 body 部分有多长
3.10 异常情况的处理
在通信过程中可能会遇到各种异常情况,TCP 对于常见的异常情况做出了相应的处理。以下,我们就来给大家介绍 TCP 对于 进程崩溃、主机关机、主机掉电 和 网线断开 等四种异常情况的处理
进程崩溃
进程崩溃,若此时 TCP 发送缓冲区没有待确认数据,实际上和主动退出并没有本质区别,都是进程释放 => 回收文件描述符表的每个资源 => 操作系统内核调用 socket 的 close 方法,即向服务器发送 FIN ,此时进程虽然已经崩溃了,但是 TCP 的连接信息还在,四次挥手还是能正常执行的(主要是由操作系统内核来执行);若触发 close 时发送缓冲区还存在待确认的数据,内核会丢弃输出缓冲区残留数据,直接发送 RST 复位报文 强制断开连接,不会发送 FIN,也不会执行四次挥手
主机关机
在电脑执行正常流程的关机时,会用一定的时间先杀死所有的用户进程,如果在这段时间内四次挥手执行完了,那就和正常断开连接是一样的;如果没有执行完(服务器返回的 FIN 太晚了,客户端来不及接收到),那么,服务器在返回 FIN 时就不会收到客户端返回的 ACK,服务器就会进行重传,在重传达到一定次数之后,服务器就会认为对端出现了严重的网络问题,就会直接主动断开连接。

主机掉电
这里的主机掉电我们要分为两种情况进行讨论,一是接收方突然掉电,二是发送方突然掉电
接收方掉电
在发送方给接收方发送数据时,接收方突然掉电了,那么,发送方后续发送的数据就不会再收到接收方返回的 ACK 了,那么发送方就会触发 “ 超时重传 ”。但是重传并不能解决问题,在重传达到一定的次数之后还是收不到 ACK,那么发送方就只能断开该连接了
发送方掉电
当发送方掉电之后,接收方就不会再收到发送方发来的数据,此时,接收方并不能区分发送方是并没有发送数据还是已经掉电了,所以接收方只能选择继续等待,但这个等待并不是无限的,在接收方等待一段时间后就会给发送方主动发送一个特殊的报文—— “ 心跳包 ”(保活探测包),这个报文并不携带任何业务数据,只是为了让发送方返回一个 ACK 来使接收方确认发送方还存活着。如果接收方多次的探测没有得到发送方的 ACK 响应,那么就可以认为发送方已经 “ 挂了 ”,接收方就会断开该连接;如果收到了,那么就说明发送方还存活着,那么接收方会接着等待发送方发送新的数据
网线断开
TCP 对于网线断开的情况和上述的主机掉电的情况的处理方式是一样的,最终都能够释放系统资源
在上文的讲解中,我们已经陆陆续续地讲到了 ACK、RST、SYN、FIN 四个常用的标志位,接下来,我们来单独讲解剩下的两个标志位
URG:紧急指针位,对于 TCP 来说,数据的接收是按照序号的大小来的,而紧急指针,就相当于 “ 插队 ” ,直接跳过前面的数据,直接从某个指定的序号开始读取
URG 是需要配合 16位紧急指针 这个字段来发挥作用的,16位紧急指针中存储的是要紧急读取的数据的最后一个字节的偏移量(字节的序号 = 起始字节序号 + 偏移量)
PSH:催促标志位,如果发送方给接收方发送的数据中带有这个标志位,接收方就会尽快地将这个数据读取到应用程序中


三. 小结
UDP 和 TCP 是传输层的两大支柱。UDP 简单、快速、无连接,适合实时性要求高、能容忍少量丢包的场景,如 DNS、视频直播、语音通话和游戏。TCP 则通过确认应答、超时重传、滑动窗口、流量控制、拥塞控制等一系列机制,实现了可靠传输,适合对数据完整性要求高的场景,如文件传输、网页浏览、邮件等
理解 UDP 和 TCP 的差异,不仅能帮你应对面试,更能让你在实际开发中做出合理的协议选型。在下一篇博客,我们将进入网络层,看看 IP 协议是如何为数据包找到正确的传输路径的
1027



被折叠的 条评论
为什么被折叠?



