什么是TCP?
基本定义与属性
- TCP(传输控制协议)是传输层的重要协议,具有面向连接(传输前需先建立连接,是发送方和接收方的点对点一对一连接)、基于字节流(以字节流形式传输数据,传输中会将字节流组织成不同大小的报文段)、可靠的特性。
核心功能
- 可靠性传输:当传输大体积数据(如视频、音频流)时,TCP 会把数据分成报文段(数据包)依次发送、按序接收。若传输中因网络问题出现丢包,TCP 会通过确认应答、超时重发等机制,保证数据能可靠到达接收方。
- 流量控制:若发送方数据发送速度过快,接收方处理不过来,接收方会通过确认报文告知发送方,让其减缓发送速度,避免接收方 “过载”。
- 拥塞控制:当网络中传输的数据包过多导致网络拥塞时,TCP 会通过慢开始、拥塞避免、快重传、快恢复等手段进行控制,防止网络拥塞情况加剧。
工作流程(结合图示)
发送方的应用进程将字节流写入发送缓存,TCP 为其加上首部构成 TCP 报文段,然后发送该报文段;接收方的 TCP 收到报文段后,从接收缓存读取字节,再交给应用进程处理,整个过程依托 TCP 连接来保障数据传输的有序与可靠。
总的来说,TCP 协议通过一系列机制,为网络数据传输提供了可靠、有序且能适应网络状况的服务,是互联网中保障数据有效传输的关键协议之一。
TCP协议报文段的格式
TCP 协议报文段是 TCP 协议用于传输数据的基本单元,它的格式设计精巧,包含了实现可靠传输、流量控制、连接管理等功能所需要的关键信息。以下是对 TCP 协议报文段格式的详细介绍:
整体结构
TCP 报文段由首部和数据部分两部分组成。首部的前 20 字节是固定的 ,后面有 4N 字节是根据需要而增加的选项(N 是整数),因此 TCP 首部的最小长度是 20 字节。
首部字段详解
- 源端口号(16 位):标识发送方应用程序使用的端口,占 16 位。通过端口号,可以让接收方知道数据是由发送方的哪个应用程序发出的,以便接收方将数据正确交付给对应的上层应用进程。例如,浏览器访问网页时,可能使用一个随机的大于 1024 的端口号作为源端口与服务器的 80(HTTP)或 443(HTTPS)端口进行通信。
- 目的端口号(16 位):标识接收方应用程序的端口,同样占 16 位。发送方依据这个端口号,将数据发送到接收方正确的应用程序。如客户端向服务器发送 HTTP 请求时,目的端口通常是 80 。
源端口号、目的端口号:
就像快递单上的 “寄件人电话” 和 “收件人电话”。
- 源端口:你(寄件人)联系电话,告诉快递站 “谁发的包裹”;
- 目的端口:商家(收件人)联系电话,告诉快递站 “包裹该送给谁”。
(比如你用淘宝 App 下单,源端口是你手机淘宝的 “临时端口”,目的端口是淘宝服务器的 “80/443 端口”)。
- 序号(32 位):用来标识从 TCP 发送端向 TCP 接收端发送的字节流,发送方发送数据时对此进行标记。在连接建立时,双方会协商一个初始序号。比如,一个 TCP 报文段携带了 1000 个字节的数据,序号为 500,那么这 1000 个字节在字节流中的位置就是从 500 到 1499 。
- 确认序号(32 位):仅当 ACK 标志位为 1 时有效。它表示接收方期望收到发送方下一个报文段的第一个数据字节的序号,用来告诉发送方哪些数据已经成功接收,哪些需要重发。例如,接收方收到了序号为 1 - 1000 的字节,期望接收下一个报文段从 1001 开始,那么确认序号就设置为 1001 。
序号、确认序号:
好比快递的 “编号 + 签收反馈”。
- 序号:包裹编号(比如 “第 500 号包裹”),方便快递站(接收方)按顺序整理;
- 确认序号:签收单(比如 “已收到 1 - 500 号包裹,下一个要 501 号”),告诉发货方 “哪些包裹收到了,接下来该发啥”。
- 首部长度(4 位):也叫数据偏移,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远,即首部的长度。由于首部中可能包含可变长度的选项部分,因此需要这个字段来明确首部的长度。该字段以 4 字节为单位,所以 TCP 首部最长为 60 字节(2^4 - 1 = 15,15 * 4 = 60 )。
首部长度:
类似 “面单有多大”。面单可能贴了 “加急贴纸”“保价标签”(对应 TCP 选项),所以得标出面单总长度,好让快递站知道 “从哪开始是包裹里的东西”。
- 保留(6 位):保留为今后使用,目前应置为 0。
- 标志位(6 位)
- URG(Urgent,紧急标志):当 URG = 1 时,表明紧急指针字段有效,告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级处理)。
- URG(紧急):“加急件!优先送!”(比如游戏里的 “紧急技能指令”,要立刻处理);
- ACK(Acknowledgment,确认标志):当 ACK = 1 时,确认序号字段才有效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置为 1。
- ACK(确认):“已收到!”(签收反馈,告诉发货方 “包裹到了”);
- PSH(Push,推送标志):当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP 就可以使用推送操作,发送方的 TCP 把 PSH 置为 1,并立即创建一个报文段发送出去;接收方的 TCP 收到 PSH = 1 的报文段,就尽快地(即 “推送” 向前)交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。
- PSH(推送):“别存着,马上送!”(比如你点外卖,商家做好就立刻送,不等凑满单);
- RST(Reset,复位标志):当 RST = 1 时,表明 TCP 连接中出现严重错误(如主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。
- RST(复位):“包裹坏了,重发!”(比如运输中包裹破损,要求重新发货);
- SYN(Synchronize,同步标志):在连接建立时用来同步序号。当 SYN = 1,ACK = 0 时,表示这是一个连接请求报文段;若对方同意建立连接,则响应报文中 SYN = 1,ACK = 1 ,用于三次握手中。
- SYN(同步):“准备发货!”(和商家建立 “发货 - 收货” 的约定,对应 TCP 三次握手的 “发起连接”);
- FIN(Finish,结束标志):用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放运输连接。
- FIN(结束):“没货了,不发了!”(告诉商家 “不再发货了,结束合作”,对应断开连接)。
- 窗口(16 位):用来让发送方设置其发送窗口的大小,单位是字节。该字段反映了接收方当前接收缓存的空闲空间大小,从而控制发送方发送数据的速率,实现流量控制。例如,接收方的接收缓存还剩 10000 字节的空间,那么它可能会在确认报文中将窗口值设置为 10000,告知发送方最多可以发送 10000 字节的数据 。
- 窗口:
好比 “收货方的仓库剩余空间”。比如你家仓库只能再放 10 个包裹,就告诉快递站 “最多再发 10 个”,防止仓库爆仓(流量控制)。
- 校验和(16 位):校验的范围包括首部和数据这两部分。在计算校验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。伪首部包含 IP 首部一些字段,其目的是让 TCP 两次检查数据是否已经正确到达目的地(例如,检查 IP 数据报是否已经到达正确的主机和端口)。
- 校验和:
像 “包裹防伪码”。快递站会检查防伪码,确保包裹在运输中没被篡改、损坏(比如零食没变质)。
- 紧急指针(16 位):仅在 URG = 1 时有效。它指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面 )。
仅当 URG = 1 时有用,类似 “加急件里,前 5 件是最急的”,告诉快递站 “紧急内容的范围”。
- 选项(长度可变):最常见的可选字段是最长报文段 MSS(Maximum Segment Size)。MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节” 。
额外的 “特殊要求”,比如 “保价 100 元”“周六再送”(对应 TCP 里的 “最大报文段长度 MSS” 等特殊设置)。
数据部分
存放应用层交付给 TCP 的数据,其长度是可变的,并且在一些情况下(如建立连接的 SYN 报文段),数据部分可能为空。
TCP 报文段的格式设计紧密围绕着其可靠传输、流量控制、拥塞控制以及连接管理等核心功能,每一个字段都在保障网络数据准确、高效传输的过程中发挥着不可或缺的作用。
三次握手(建立连接)
TCP 的三次握手是建立连接时客户端和服务器之间的 “三次对话”,目的是确认双方都能正常收发数据,就像打电话时互相确认 “能听到吗” 一样。咱们用 “打电话” 的场景通俗讲解:
三次握手的完整过程
假设 客户端 是你,服务器 是朋友:
1. 第一次握手:你先 “拨号”
- 客户端→服务器:发送一个 “SYN” 标志的报文(类似你打电话时说:“喂,是小明吗?能听到我说话吗?”)。
- 报文里会带上客户端的 “初始序号”(比如 x),表示 “我接下来要发的数据就从这个序号开始算”。
- 此时客户端状态:“我已发出请求,等待对方回应”(SYN-SENT)。
2. 第二次握手:朋友 “回应”
- 服务器→客户端:收到请求后,回复一个 “SYN+ACK” 标志的报文(类似朋友说:“我能听到你!你能听到我吗?”)。
- 报文里包含两个关键信息:
- 服务器的 “初始序号”(比如 y),表示 “我接下来发的数据从这个序号开始”;
- 确认序号(x+1),表示 “我已收到你序号 x 的请求,下次请从 x+1 开始发”。
- 此时服务器状态:“我已回应请求,等待对方确认”(SYN-RCVD)。
3. 第三次握手:你 “确认”
- 客户端→服务器:收到回应后,再发一个 “ACK” 标志的报文(类似你说:“我也能听到你!那我们开始聊吧~”)。
- 报文里的确认序号是(y+1),表示 “我已收到你序号 y 的回应,下次请从 y+1 开始发”。
- 此时客户端状态:“连接已建立”(ESTABLISHED);服务器收到后也进入 “连接已建立” 状态,双方可以开始传输数据了。
1.为什么需要三次握手?
核心是为了 防止 “已失效的连接请求” 干扰正常通信。
举个例子:
如果客户端很久以前发的一个连接请求(因网络延迟)突然传到服务器,服务器以为是新请求,就会回应并建立连接,但客户端其实已经不需要了,这会浪费服务器资源。
三次握手通过 “客户端最后一次确认”,确保双方都认可 “这是一个有效的新连接”,避免上述问题。
2.TCP 三次握手建立连接的过程中,客户端和服务器会经历不同的状态变化?
三次握手的状态变迁(客户端 vs 服务器)
初始状态
- 客户端:处于 CLOSED(关闭) 状态(未发起任何连接请求)。
- 服务器:处于 LISTEN(监听) 状态(已准备好接收连接请求,比如 Web 服务器启动后等待浏览器连接)。
第一次握手:客户端发起连接请求
- 客户端动作:向服务器发送 SYN 报文(包含客户端初始序号),请求建立连接。
- 客户端状态:从
CLOSED
→ SYN-SENT(同步已发送)(表示 “已发请求,等待服务器回应”)。 - 服务器状态:仍为
LISTEN
(尚未收到请求)。
第二次握手:服务器回应请求
- 服务器动作:收到客户端的 SYN 报文后,回复 SYN+ACK 报文(包含服务器初始序号和对客户端的确认序号)。
- 服务器状态:从
LISTEN
→ SYN-RCVD(同步已接收)(表示 “已收到请求并回应,等待客户端确认”)。 - 客户端状态:仍为
SYN-SENT
(尚未收到服务器回应)。
第三次握手:客户端确认连接
- 客户端动作:收到服务器的 SYN+ACK 报文后,发送 ACK 报文(包含对服务器的确认序号)。
- 客户端状态:从
SYN-SENT
→ ESTABLISHED(已建立连接)(表示 “双方已确认,连接就绪”)。 - 服务器动作:收到客户端的 ACK 报文后,确认连接建立。
- 服务器状态:从
SYN-RCVD
→ ESTABLISHED(已建立连接)。
状态总结表
阶段 | 客户端状态 | 服务器状态 | 核心动作 |
初始阶段 | CLOSED(关闭) | LISTEN(监听) | 服务器等待连接,客户端未发起请求 |
第一次握手后 | SYN-SENT(同步已发送) | LISTEN(监听) | 客户端发 SYN,等待服务器回应 |
第二次握手后 | SYN-SENT(同步已发送) | SYN-RCVD(同步已接收) | 服务器发 SYN+ACK,等待客户端确认 |
第三次握手后 | ESTABLISHED(已建立) | ESTABLISHED(已建立) | 客户端发 ACK,双方连接就绪,可传数据 |
通俗类比:用 “约饭” 理解状态
- CLOSED(客户端):你还没打算约朋友吃饭(未行动)。
- LISTEN(服务器):朋友说 “我今天有空,随时可以约”(等待被约)。
- SYN-SENT(客户端):你发消息问朋友 “今晚一起吃饭吗?”(已发起请求,等回复)。
- SYN-RCVD(服务器):朋友回消息 “好啊!你想吃火锅还是烧烤?”(已回应,等你确认)。
- ESTABLISHED(双方):你回 “吃火锅吧!”(确认达成一致,约饭成功,可进一步讨论时间地点)。
关键说明
- 状态变迁的核心是 “确认双方收发能力”:每次状态变化都对应一次报文交互,直到双方都进入
ESTABLISHED
状态,才意味着连接正式建立,可开始传输数据。 - 如果握手过程中出现超时(如客户端发 SYN 后没收到回应),客户端会重发 SYN 报文,多次失败后会放弃连接,回到
CLOSED
状态。
理解这些状态,有助于排查连接建立失败的问题(比如服务器长期停留在 SYN-RCVD
状态,可能是客户端未发送第三次握手的 ACK 报文)。
四次挥手 (断开连接)
TCP的四次挥手是断开连接时客户端和服务器之间的“四次对话”,整个过程中双方会经历不同的状态变化,就像“结束通话前的礼貌告别流程”。以下是具体状态及对应过程:
四次挥手的状态变迁(客户端 vs 服务器)
初始状态
- 客户端和服务器:均处于 ESTABLISHED(已建立连接) 状态(正在传输数据或待命)。
第一次挥手:客户端发起断开请求
- 客户端动作:当客户端数据发送完毕,向服务器发送 FIN报文(表示“我这边数据发完了,准备断开”)。
- 客户端状态:从
ESTABLISHED
→ FIN-WAIT-1(终止等待1)(表示“已发断开请求,等待服务器确认”)。 - 服务器状态:仍为
ESTABLISHED
(尚未收到断开请求)。
第二次挥手:服务确认收到请求
- 服务器动作:收到客户端的FIN报文后,立即回复 ACK报文(表示“我收到你的断开请求了,但我这边可能还有数据没发完,请等一下”)。
- 服务器状态:从
ESTABLISHED
→ CLOSE-WAIT(关闭等待)(表示“已确认客户端要断开,正在处理剩余数据”)。 - 客户端状态:从
FIN-WAIT-1
→ FIN-WAIT-2(终止等待2)(表示“收到服务器确认,等待服务器发它的断开请求”)。
第三次挥手:服务器发送断开请求
- 服务器动作:当服务器剩余数据发送完毕,向客户端发送 FIN报文(表示“我这边数据也发完了,可以断开了”)。
- 服务器状态:从
CLOSE-WAIT
→ LAST-ACK(最后确认)(表示“已发断开请求,等待客户端最后确认”)。 - 客户端状态:仍为
FIN-WAIT-2
(尚未收到服务器的FIN报文)。
第四次挥手:客户端确认断开
- 客户端动作:收到服务器的FIN报文后,发送 ACK报文(表示“收到你的断开请求,确认断开”),并等待一段时间(2MSL,确保服务器收到确认)。
- 客户端状态:从
FIN-WAIT-2
→ TIME-WAIT(时间等待) → 最终变为CLOSED(关闭)
(等待超时后彻底关闭)。 - 服务器动作:收到客户端的ACK报文后,确认断开。
- 服务器状态:从
LAST-ACK
→ CLOSED(关闭)。
状态总结表
阶段 | 客户端状态 | 服务器状态 | 核心动作 |
初始阶段 | ESTABLISHED(已建立) | ESTABLISHED(已建立) | 双方正常连接,可传输数据 |
第一次挥手后 | FIN-WAIT-1(终止等待1) | ESTABLISHED(已建立) | 客户端发FIN,等服务器确认 |
第二次挥手后 | FIN-WAIT-2(终止等待2) | CLOSE-WAIT(关闭等待) | 服务器发ACK,处理剩余数据 |
第三次挥手后 | FIN-WAIT-2(终止等待2) | LAST-ACK(最后确认) | 服务器发FIN,等客户端确认 |
第四次挥手后 | TIME-WAIT(时间等待)→ CLOSED | CLOSED(关闭) | 客户端发ACK,等待超时后彻底关闭 |
通俗类比:用“结束通话”理解状态
- ESTABLISHED:你和朋友正在通话(正常聊天)。
- FIN-WAIT-1:你说“我没什么要说的了,挂了啊?”(发FIN,等朋友回应)。
- CLOSE-WAIT:朋友说“知道了,我还有最后一句话”(发ACK,处理剩余内容)。
- FIN-WAIT-2:你听着朋友说最后一句话(等朋友说完)。
- LAST-ACK:朋友说“我说完了,挂吧”(发FIN,等你确认)。
- TIME-WAIT:你说“好的,再见”(发ACK),并等几秒再挂(确保朋友听到)。
- CLOSED:双方都挂了电话(连接彻底关闭)。
关键说明
- TIME-WAIT的作用:客户端等待2MSL(报文最大生存时间),是为了确保服务器能收到最后的ACK(如果服务器没收到,会重发FIN,客户端可再次确认),避免服务器因没收到确认而一直处于
LAST-ACK
状态。 - CLOSE-WAIT的意义:服务器在
CLOSE-WAIT
状态停留的时间,取决于它处理剩余数据的耗时(如果长期停留,可能是服务器程序有bug,没及时处理完数据)。
理解这些状态,有助于排查连接断开的问题(比如客户端长期处于TIME-WAIT
可能导致端口耗尽,服务器长期CLOSE-WAIT
可能是代码未释放连接)。
重传机制:
在TCP协议的可靠传输机制中,超时重传、快速重传和SACK(选择性确认)是三种关键技术,它们各有侧重又相互配合。下面分别用专业和通俗的方式讲解:
一、超时重传(Timeout Retransmission)
专业解释:
超时重传是最基础的重传机制。发送方在发送数据后会启动一个计时器(超时时间RTO),如果在计时器到期前没有收到接收方的确认(ACK),就认为数据丢失,会重新发送该数据。
超时时间(RTO)不是固定值,会根据网络状况动态调整(比如网络延迟高时,RTO会自动增大)。
通俗理解:
就像你给朋友发微信消息,设置了"5分钟没回复就再发一次"。如果5分钟内没收到朋友的回复(可能消息丢了,也可能朋友没看到),你就再发一次同样的消息。
特点:
- 优点:实现简单,是所有可靠传输的基础保障
- 缺点:等待超时时间可能较长,影响传输效率(比如网络只是短暂波动,却要等完整的超时时间)
二、快速重传(Fast Retransmit)
专业解释:
当发送方连续收到3个相同的重复确认(Duplicate ACK)时,无需等待超时计时器到期,就立即重传未被确认的数据。
重复确认指的是:接收方收到了乱序的数据,会重复发送已正确接收的最后一个数据的ACK。例如,发送方发送1、2、3、4、5,接收方收到1、3,会连续发送3次"已收到1"的ACK,此时发送方就知道2丢失了,会立即重传2。
通俗理解:
你给朋友发了5条连续的消息:消息1、消息2、消息3、消息4、消息5。
朋友收到了消息1和消息3,但没收到消息2,于是连续回复你3次"我收到消息1了"。
你一看,收到3次同样的回复,就知道朋友肯定没收到消息2,不等5分钟超时,马上重发消息2。
特点:
- 优点:比超时重传更及时,减少了等待时间,提高了传输效率
- 缺点:只能判断出"有数据丢失",但不知道具体丢失了哪些数据(当丢失多个连续数据时效率不高)
三、SACK(Selective Acknowledgment,选择性确认)
专业解释:
SACK是对快速重传的增强,允许接收方在ACK中明确告知发送方"哪些数据已经收到",而不仅仅是"最后一个正确收到的数据"。
通过SACK,接收方可列出已正确接收的所有数据段范围,发送方就能精确知道哪些数据丢失了,只重传丢失的部分,而不用重传后续所有数据。
通俗理解:
你给朋友发了10条消息(1-10),朋友收到了1-3、6-10,但没收到4-5。
没有SACK时,朋友只能说"我收到3了";
有SACK时,朋友会明确告诉你"我收到了1-3和6-10"。
你一看就知道只有4-5丢了,只重发这两条即可,不用重发6-10。
特点:
- 优点:能精确识别丢失的数据,减少不必要的重传,极大提高传输效率,特别适合网络状况较差、经常有数据丢失的场景
- 缺点:需要双方支持该选项,增加了协议的复杂度
三者关系总结:
- 超时重传是"保底机制",无论其他机制是否生效,超时后一定会重传
- 快速重传是"效率优化",通过重复ACK提前发现丢失,不用等超时
- SACK是"精确制导",让重传更精准,避免无效重传
实际网络中,这三种机制通常一起工作:先尝试SACK+快速重传实现高效修复,若失败则等待超时重传作为最后保障,共同保证数据可靠且高效地传输。
滑动窗口
9.1. 概述
滑动窗口是 TCP 协议中用于实现流量控制和高效数据传输的一种机制。它允许发送方在等待接收方确认的同时,连续发送多个数据段,从而提高网络利用率,同时避免发送过多数据导致接收方缓冲区溢出。
9.2. 什么是窗口?
- 专业详解:窗口本质上是一个大小可变的缓冲区,对于发送方来说,发送窗口表示在未收到确认的情况下,最多可以发送的数据量;对于接收方来说,接收窗口表示自己当前能够接收的数据量。窗口的大小由接收方根据自身的缓冲区情况来决定,并通过 ACK 报文告知发送方。
- 通俗理解:想象发送方是一个快递发货站,接收方是一个快递代收点。窗口就像是代收点能容纳包裹的空间,发货站在代收点确认收到包裹之前,最多能发出去的包裹数量就是发送窗口大小,代收点能接收的包裹数量就是接收窗口大小。
9.3. 窗口大小由谁决定?
窗口大小主要由接收方决定。接收方会根据自己的接收缓冲区剩余空间来设置接收窗口大小,并通过 ACK 报文将这个信息告知发送方。发送方的发送窗口大小不能超过接收方告知的接收窗口大小。
- 通俗理解:还是以快递为例,代收点能收多少包裹,就告诉发货站 “我最多还能收这么多”,发货站就按照代收点说的数量来发货。
9.4. 发送方的滑动窗口
- 专业详解:发送方的滑动窗口包含了已经发送但未被确认的数据段,以及可以继续发送的数据段。随着接收方不断返回确认信息,窗口会不断向右滑动,新的数据段可以被发送。
- 通俗理解:发货站有一个待发货的包裹队列,滑动窗口就像是一个框,框内的包裹是可以直接发货的。当收到代收点对框内部分包裹的确认后,框就会向右移动,后面的包裹就进入了可发货范围。
9.5. 实现机制
通过 TCP 报文中的序号(Seq)和确认号(Ack),以及窗口字段来实现。发送方根据接收方告知的窗口大小,控制发送数据的量;接收方在收到数据后,返回包含确认号和窗口大小的 ACK 报文,告知发送方哪些数据已接收,以及还能接收多少数据。
9.6. 作用
- 流量控制:防止发送方发送数据过快,导致接收方缓冲区溢出。
- 提高效率:允许发送方连续发送多个数据段,减少了等待确认的时间,提高了网络利用率。
9.7. 窗口滑动过程
可以参考之前你提供的那张窗口滑动过程图,发送方发送数据,接收方确认数据,随着确认的进行,发送方的窗口不断向右滑动,允许发送更多的数据。
10. 流量控制
- 专业详解:流量控制是一种端到端的控制机制,主要是为了防止发送方发送数据的速率过快,导致接收方来不及处理,造成数据丢失。它通过接收方告知发送方自己的接收窗口大小,来限制发送方的发送速率。
- 通俗理解:还是用快递的例子,发货站发货太快,代收点来不及接收和存放包裹,就会告诉发货站 “你慢点发,我现在只能收这么多”,以此来控制发货的流量。
11. 拥塞控制
11.1. 拥塞控制与流量控制的区别
- 专业详解:流量控制是端到端的,关注的是发送方和接收方之间的关系,防止接收方缓冲区溢出;而拥塞控制是全局性的,关注的是网络整体的状况,防止网络出现拥塞(如链路拥塞、路由器过载等)。拥塞控制通过调整发送方的发送速率,来适应网络的承载能力。
- 通俗理解:流量控制就像是发货站和代收点之间的协调,避免代收点爆仓;拥塞控制则像是整个快递运输网络的管理,避免交通拥堵,比如当很多发货站都在大量发货,导致运输路线拥堵时,就需要控制发货站的发货速度。
这是 TCP 拥塞控制四大算法(慢启动、拥塞避免、拥塞发生、快速恢复)的完整流程拆解,结合图和实际场景讲透逻辑:
一、核心目标:TCP 如何 “聪明地发数据”?
网络就像高速公路,数据是汽车:
- 发太快 → 堵车(网络拥塞),数据丢包、延迟高;
- 发太慢 → 浪费带宽,传输效率低
TCP 用 拥塞窗口(cwnd) 当 “油门”:cwnd 越大,发得越快;cwnd 越小,发得越慢。
四大算法的作用:动态调整 cwnd,让网络 “不堵车又高效”。
二、1. 慢启动(Slow Start):“轻轻踩油门,试探路况”
核心逻辑:
- 初始
cwnd = 1
(只能发 1 个数据包); - 每收到 1 个确认(ACK),
cwnd 翻倍
(指数增长:1→2→4→8…); - 直到
cwnd 触达慢启动门限(ssthresh)
,切换到拥塞避免。
图中流程(以轮次理解):
- 轮次 1:
cwnd=1
,发 1 个包(M₁),收到 ACK 后,cwnd=2
; - 轮次 2:
cwnd=2
,发 2 个包(M₂~M₃),收到 ACK 后,cwnd=4
; - 轮次 3:
cwnd=4
,发 4 个包(M₄~M₇),收到 ACK 后,cwnd=8
; - …… 直到
cwnd
触达ssthresh
(图中是 8),进入拥塞避免。
通俗类比:
你上高速前,不知道路况,先以 10km/h(cwnd=1
)试探,发现路空,加到 20km/h(cwnd=2
),再发现路还空,加到 40km/h(cwnd=4
)…… 直到接近 “限速”(ssthresh
),才稳定加速。
三、2. 拥塞避免(Congestion Avoidance):“稳定加速,别搞堵车”
核心逻辑:
- 当
cwnd ≥ ssthresh
时,进入拥塞避免; - 每收到一轮 ACK(所有发送的包都确认),
cwnd 只加 1
(线性增长:8→9→10…)。
图中流程:
ssthresh=8
,cwnd
到 8 后,每轮次只 +1:
轮次 4:cwnd=8
→ 发 8 个包 → 收 ACK →cwnd=9
;
轮次 5:cwnd=9
→ 发 9 个包 → 收 ACK →cwnd=10
;
…… 缓慢增长,避免网络拥塞。
通俗类比:
接近高速限速(ssthresh
)后,保持 “稳定加速”,比如从 80km/h 慢慢加到 81、82…,别突然飙到 120 搞堵车。
四、3. 拥塞发生(Congestion Occurs):“堵车了!快刹车”
触发条件:
网络丢包时,TCP 认为 “堵车了”,触发拥塞发生。丢包的判断有两种:
- 超时重传:发完包后,超时没收到 ACK(路彻底堵死,包没回来);
- 快速重传:收到 3 个重复 ACK(路没全堵,但有包丢了,后面的包先到了)。
处理逻辑(分场景):
场景 1:超时重传(图中左半部分)
ssthresh = cwnd/2
(比如cwnd=12
→ssthresh=6
);cwnd=1
(急刹车,重新慢启动);- 重新从
cwnd=1
开始指数增长,直到触达新的ssthresh
。
场景 2:快速重传(图中未单独画,但为快速恢复铺垫)
- 收到 3 个重复 ACK,认为 “丢了一小部分包”,触发快速恢复(下文讲)。
通俗类比:
- 超时重传:高速彻底堵死(包超时没回来),你只能刹车到 10km/h(
cwnd=1
),重新试探; - 快速重传:高速上有车祸(丢了几个包),但还能缓慢移动,不用彻底刹车。
五、4. 快速恢复(Fast Recovery):“小堵车,缓缓开”
核心逻辑(基于快速重传触发):
ssthresh = cwnd/2
(比如cwnd=12
→ssthresh=6
);cwnd = ssthresh + 3
(因为收到 3 个重复 ACK,说明有 3 个包已经 “在路上”,可以稍微加速);- 之后每收到 1 个 ACK,
cwnd +1
(线性增长,恢复传输)。
图中流程:
- 触发快速重传后,
cwnd
从ssthresh+3
开始,缓慢增长回到拥塞避免阶段。
通俗类比:
高速上遇到小事故(丢包),但车流还能动,你把速度降到 63km/h(ssthresh+3
),然后慢慢加到 70、80…,恢复正常。
六、四大算法的协同关系
- 慢启动:从 0 开始,快速试探网络(指数增长);
- 拥塞避免:触达门限后,稳定增长(线性增长);
- 拥塞发生:丢包时,急刹车(超时重传)或缓刹车(快速重传);
- 快速恢复:缓刹车后,慢慢恢复速度。
本质是 TCP 的 “自适应策略”:先试探、再稳定、遇堵刹车、小堵缓恢复,让网络在 “高效” 和 “不拥塞” 之间找平衡。
总结
记住一个比喻:
TCP 像 “聪明的司机”,用 慢启动试探高速(网络)路况,拥塞避免稳定加速,遇到堵车(丢包)时,严重堵车就彻底刹车重启(超时重传),小堵车就缓刹车慢慢恢复(快速重传 + 快速恢复),最终让数据传输又快又稳。
12. TCP 协议如何保证可靠性传输?
- 专业详解:通过序列号和确认号机制,确保数据按序到达;通过重传机制(超时重传、快重传),保证丢失的数据能够被重新发送;通过校验和,检测数据在传输过程中是否发生错误;通过流量控制和拥塞控制,避免网络拥塞和接收方缓冲区溢出,保证数据稳定传输。
- 通俗理解:就像寄重要文件,给每个文件编号(序列号),对方收到后确认(确认号);如果没收到或者文件有问题,就重新寄(重传);寄之前检查文件是否完整(校验和);还要根据对方接收能力和运输路况(流量控制和拥塞控制)来调整寄件速度。
13. TCP 粘包
13.1. 产生原因
- 专业详解:TCP 是面向字节流的协议,发送方将数据写入套接字缓冲区,接收方从套接字缓冲区读取数据。当发送方连续发送多个小数据段时,这些数据可能会被合并成一个大的数据段发送;而接收方读取数据时,也可能一次性读取了多个发送方的数据,导致数据边界不清晰,产生粘包现象。
- 通俗理解:你要发送多条短消息,这些消息在传输过程中可能被 “粘” 在一起发送,接收方收到后分不清哪条消息是哪条。
13.2. 解决办法
- 消息定长:发送方和接收方约定好每个消息的固定长度,接收方每次按固定长度读取数据。
- 添加消息边界:在每个消息末尾添加特殊的边界标识,接收方根据边界标识来区分不同的消息。
- 在消息头中记录消息长度:发送方在消息头中写入消息的实际长度,接收方先读取消息头获取消息长度,再按长度读取完整的消息。