上一节拆开了 TCP 头、认出了序号和确认号两个字段。这一节看它们真干活的样子:应用层交来一大坨数据(动辄几万、几百 KB),TCP 先把它切成一段一段(每段不超过 MSS),给每段编上起始字节号,对面按号确认、按号排队,缺哪一段就盯着那个号一直要。切、编、对、拼——这四步就是标题说的"分割处理"。
3.18 是拆发动机——TCP 头里有些什么字段、四条支柱(编号 / 确认 / 重传 / 窗口)叫什么名字。 本节是看发动机怎么带动轮子——一个大文件真的被切成几段、"本段序号 = 上段序号 + 上段长度"这个算式怎么递推、丢一段时 ACK 号会怎么反应。 一句话:3.18 给的是零件清单,本节是流水线实拍。
教材这一节用的是序列号,前面几节我写的是序号,抓包里显示的是 Sequence Number / Seq——三个说的是同一个字段。下面统一写"序号",看到教材的"序列号"别以为是什么新东西。
一、为什么非切不可:三个"装不下"
应用层把数据交给 TCP 的时候,是一整坨——write() 一次写了 3 MB,TCP 就收到 3 MB 的字节流。它能不能"一票发出去"?不能,撞三堵墙(3.4 埋过这三堵墙,本节把它们对准 TCP):
| 堵墙 | 具体 |
|---|---|
| ① 链路过不去 | 一条以太网帧最多 1500 字节数据(MTU),一大坨根本塞不进任何一个帧 |
| ② 出错代价大 | 一小段坏了只补那一小段;一整坨坏了得从头再来 3 MB |
| ③ 不能轮流 | 一坨货独占线路,别人的视频、语音全得排队 |
所以 TCP 必须自己先切。注意这里说的是"TCP 自己切"——和"IP 被迫分片"是两回事,第五节专门对比。
3.4 也讲了"切块",但它是为了讲清包长什么样(头部 + 数据),所以切块只是一句带过。 本节的主角就是切这个动作本身——切多大、怎么编号、对面怎么按号点收。
二、切多大:MSS 登场(本节第一个新概念)
切块不能随便切,得有把尺子。这把尺子叫 MSS(Maximum Segment Size,最大报文段长度)。
它的算法简单到只有一步减法:
MSS = MTU − IP 头 − TCP 头
= 1500 − 20 − 20
= 1460 字节
为什么要减掉两个头? 因为"1500"是整个帧能装的数据上限,而这一段路要装的是「IP 头 + TCP 头 + 你的数据」三样。两个头各占 20 字节,剩下的才是留给数据的——1460。
3.4 说过一句关键话:分片是 IP 的活,不是 TCP 的活。这句话背面还有一层没展开的意思——
IP 分片是有代价的(多贴邮票、丢一片整包废),所以正确的做法不是"发出去让 IP 去切",而是 TCP 在源头就切好,切到刚好不触发分片。MSS 这个数字就是干这个用的。
这是本节最值钱的一条认知:好的设计把问题解决在上游。TCP 主动切,IP 就不用被动切。
MTU 和 MSS 别混:一个在链路层,一个在传输层
| MTU | MSS | |
|---|---|---|
| 全称 | Maximum Transmission Unit | Maximum Segment Size |
| 中文 | 最大传输单元 | 最大报文段长度 |
| 归哪层 | 网络接口层(链路自己的体质) | 传输层(TCP 协商出来的约定) |
| 典型值 | 以太网 1500 | 以太网 + IPv4 → 1460 |
| 谁定 | 链路技术(网卡、光猫、隧道) | 握手时两端商量 |
| 管什么 | 整个帧最大装多少 | 一段里数据部分最大装多少 |
| 超了怎样 | IP 分片 | 不成立——TCP 本来就不会切这么大 |
3.5 讲握手时说了两件顺带办的事(我也想连你、我的起始序号)。现在是第三件:
SYN 报文里还塞了一个 MSS 选项——双方各自报出"我这边最多能接 1460 字节一段",然后取小的那个。
为什么要取小的?因为数据可能横跨两种链路,必须迁就窄的那一头(收 1.4:互联网是千网互联)。你这边是万兆以太网、对面走 PPPoE 宽带(MTU 只有 1492),那就得按 1452 来。
1460 只是"IPv4 + 以太网"这个最常见组合的结果。换个环境就变:
| 环境 | MTU | MSS(IPv4) |
|---|---|---|
| 以太网 | 1500 | 1460 |
| PPPoE 拨号 | 1492 | 1452 |
| 常见 VPN / 隧道 | 1400 上下 | 1360 上下 |
| 本机回环 | 65535 | 65495 |
所以"测一测自家 MSS"是有实际意义的动作——Demo33 里会做。
三、切完怎么编号(本节第一张图)
尺子有了,动手切。假设:
- 应用层交来 10000 字节
- MSS = 1460
- 握手时谈好的起始序号是 1001(为什么不是 0,见 3.18 的"起始序号是随机挑的")
按 1460 一段切下去,能切出 7 段(6 个满段 + 1 个尾巴):
| 段 | 序号 | 长度 | 覆盖的字节号 |
|---|---|---|---|
| 段 1 | 1001 | 1460 | 1001 ~ 2460 |
| 段 2 | 2461 | 1460 | 2461 ~ 3920 |
| 段 3 | 3921 | 1460 | 3921 ~ 5380 |
| 段 4 | 5381 | 1460 | 5381 ~ 6840 |
| 段 5 | 6841 | 1460 | 6841 ~ 8300 |
| 段 6 | 8301 | 1460 | 8301 ~ 9760 |
| 段 7 | 9761 | 1240 | 9761 ~ 11000 |
算号的全部规则就一条:
本段序号 = 上段序号 + 上段的长度
1001 + 1460 = 2461 → + 1460 = 3921 → + 1460 = 5381 …… 一路加到 9761,最后一段只剩 1240 字节的余量,装多少算多少。
核对一下:6 × 1460 + 1240 = 8760 + 1240 = 10000 ✅ 不多不少,正好装完。
① 尾段不用补齐。 最后一段 1240 字节就直接发 1240,不补空字节。这一点和"定长协议"不同——TCP 靠 IP 头的总长度就能知道这段有多长,不需要靠"补到 1460"来定界。
② 序号是连续的两个报文的差距,不是"第几段"。 段 4 的序号是 5381,不是"4"。它俩之间差了 1460——正因为按字节连续编,中间丢一段、重传一段,号码都还是原样,不需要重新编号。
③ 序号只在"同一条连接内"有意义。 换个连接,起始序号就是另一个随机数,两边的号码没有可比性(3.18 说过为什么随机)。
四、对面怎么对账:ACK 号(本节第二张图)
切好、编好、发出去。对面收到之后干什么?回一个号。
ACK 号(确认号)= 我下一个期望收到的字节编号。
也就是说:这个号之前的字节,我全收到了;从这个号开始,我还没收到。
还是上面那 7 段,逐段看:
| 收到 | ACK 号 | 含义 |
|---|---|---|
| 段 1(1001~2460) | ACK 2461 | 2460 之前的都收到了,我要 2461 |
| 段 2(2461~3920) | ACK 3921 | 3920 之前的都收到了,我要 3921 |
| 段 3 丢了 | —— | 没收到,没应答 |
| 段 4(5381~6840)到了 | ACK 3921 | 我还在等 3921 |
| 段 5 到了 | ACK 3921 | 我还在等 3921 |
| 段 6 到了 | ACK 3921 | 我还在等 3921 |
这里就是本节最该看懂的一处
段 4、5、6 明明都送到了,为什么回的 ACK 号还停在 3921?
因为累积确认只认"缺口的起点"(3.18 讲过这个词,这里是它的现场):
- 段 4 是 5381~6840 这一段,它后面的东西
- 而 3921~5380 那段还空着
- TCP 要交给程序的是一段连续的字节流,中间有个洞,后面的就接不上
- 所以接收方只能一遍遍说:"我缺的是 3921,给我 3921"
段 4、5、6 各触发一次重复 ACK,加起来就是 3 个重复 ACK——这正是 3.18 讲的快速重传的触发条件:
发送方收到 3 次"我还在等 3921",立刻明白不是晚到了、是真丢了,马上重传段 3,不用干等超时闹钟。
这条机制正是靠本节这套"序号 + ACK 号"才跑得起来——没有按字节连续编号,就没有"重复 ACK"这个概念。
常见误读:"既然 ACK 不动,那发段 4、5、6 不是白费?"
不白费。 接收方有个接收缓冲区:段 4、5、6 到了之后先存起来,只是"还不能交给程序"。等段 3 补上,3921 一直到 8300+ 就一次性全交上去了。
所以 TCP 的重传代价其实比想象中小——只需要补那一小段,后面的不用重来。这正是第一节讲的"局部重传"在字节号体系下真正落地的地方。
顺带把"ACK 号不等于收到了"再钉一次
ACK 3921 ≠ 我收到了 3921 这个字节。
是"3921 之前的全收到了,从 3921 开始还没收到"。差这一个字节,重传范围就判错了(3.18 的误区 2,本节的表又演示了一遍)。
五、切段 vs 分片:两个"切"千万别混(本节最值钱的一张表)
现在可以把一笔大账结清了。网络里有两个"切",名字都带"分",但性质完全不同:
| TCP 切段 Segmentation | IP 分片 Fragmentation | |
|---|---|---|
| 谁动手 | TCP(传输层) | IP(网际层) |
| 在哪切 | 源头,一次性切好 | 中途,遇到窄链路被迫切 |
| 切出来是什么 | 独立的报文段——每段有自己的序号 | 同一个包的一部分——分片之间是一体同命 |
| 丢了怎么办 | 只补丢的那一段 | 整包全部重来(少一片都拼不出来) |
| 对端看得见吗 | 看得见(每段独立编号、独立确认) | 看不见(对端 IP 悄悄拼好再交给 TCP) |
| 正常吗 | 正常状态,每一段数据都经历它 | 要尽量避免的意外 |
一句话:
TCP 切段是"主动规划",IP 分片是"被迫拆解"。
而 MSS 的使命就是让 IP 永远不必分片——TCP 在源头就把段切到刚好能过,IP 层一路上都是轻装通过。
为什么有时候"换了个网络,网页就卡"?因为新链路 MTU 更小,如果 MSS 没跟着调小,TCP 切出来的段就会超重,逼得 IP 在中途分片——一次意外分片,就多一份丢包风险(丢一片整包废,收 3.4)。
这也是路径 MTU 发现(PMTUD)、MSS Clamping 这些技术存在的原因——都是为了把"被迫分片"这件事消灭掉。
六、序号不是无限的:32 位会绕回来
序号字段是 32 位,能表示的数是 2³² = 4,294,967,296 ≈ 4.29 GB。
也就是说,一条连接每传完约 4.29 GB,序号就绕回 0 重来一次(术语叫序号回绕,wraparound)。绕一圈要多久?
| 链路速率 | 绕一圈的耗时 |
|---|---|
| 100 Mbps(12.5 MB/s) | 约 5.7 分钟 |
| 1 Gbps(125 MB/s) | 约 34 秒 |
| 10 Gbps(1250 MB/s) | 约 3.4 秒 |
在万兆网络下,序号 3 秒多就重来一遍。
那问题来了:新数据的序号和几秒前的老数据序号撞了怎么办? 比如一个很晚才到、其实早该丢弃的旧段,怎么知道它是"上一圈的"而不是"这一圈的"?
答案是时间戳选项(TCP Timestamp,RFC 1323):每个报文段都带一个时间戳,接收方靠它把"上一圈的段"和"这一圈的段"分开,这个机制叫 PAWS(Protection Against Wrapped Sequences,防序号回绕)。
为什么现代 TCP 默认开着"时间戳选项"? 不是为了测时间,而是为了防序号撞车。抓包时看到每个段都挂着 TSval / TSecr 两个数,就是它。
这也解释了一段历史:早期网卡和设备在高速链路上出过莫名其妙的串包问题,根子就在这个 32 位序号上。
七、两个该知道的小规矩
1. SYN 和 FIN 也各占一个序号
序号是按字节编的(3.18),但有两个"不装数据却占号"的特殊段:SYN 和 FIN。
所以握手时 SYN 报的序号是 X,第一个数据字节的序号就是 X + 1——不是 X。
例:SYN 的序号是 1000 → 第一个数据字节是 1001(本节例子里那个 1001 就是这么来的,自洽)→ 挥手时 FIN 也会再吃掉一个号。看抓包时对不上号,十有八九是忘了这两个家伙也占位。
2. 窗口报 0 的时候,发的是个"1 字节的探子"
接收方缓冲区满了会报窗口 0,发送方随即停发(收 3.18 的流量控制)。但它不能就这么死等——万一对面腾出位置了却不通知呢?所以它会定期发一个只带 1 个字节的探测包去试门。这个"1 字节探子"的序号也是正常递推的。
八、动手 Demo33:把"切"和"编"看个实在
- 看本机的段账本
netstat -s -p tcp找「发送的段」Segments Sent与「接收的段」Segments Received两项。 - 打开一个网页、或下载一个大文件,再跑一遍——两个数字都会明显上涨
-
涨的那部分,就是本节讲的"切出来的段"一段段出去的
-
用段数 ÷ 字节数,验证 MSS(本节最实证的一步)
如果输出里有「发送的字节」Bytes Sent 这一项(不同系统项名可能不同,有就算):
平均段长 ≈ 发送的字节数 ÷ 发送的段数
算出来应当在 1400 出头——因为每段最多 1460,而且不是每段都装满(尾段、小请求会拉低平均)。能算出这个数,就说明 TCP 真的在按 MSS 大小切。
- 从 MTU 倒推 MSS,把两个算式对上(收 3.4 的 Demo18)
先用 ping 摸出自家 MTU:
ping -f -l 1472 www.baidu.com
通了说明 MTU = 1500(因为 1472 + 20(IP 头) + 8(ICMP 头) = 1500)。
于是可以倒推:
MSS = 1500 − 20(IP 头) − 20(TCP 头) = 1460
一个很漂亮的对照
1472和1460差 12。这 12 是哪来的? 是 ICMP 头(8 字节)和 TCP 头(20 字节)的差:20 − 8 = 12。所以两个算式其实是同一件事: - ping:
数据 1472 + IP 头 20 + ICMP 头 8 = 1500- TCP:数据 1460 + IP 头 20 + TCP 头 20 = 1500都是"数据 + 各种头 = MTU",只是夹在中间的"头"换了个身份。
-
看自家接口的 MTU 清单
netsh interface ipv4 show subinterface看 「MTU」 列。有线网卡、Wi-Fi、回环的值可能各不同;连过 VPN 之后再看,会多出一条 MTU 明显偏小的条目——那就是"路变窄了",MSS 得跟着让。 -
30 秒实验:数一数"打开这个网页"用了多少段
netstat -s -p tcp记下「接收的段」数字 → 刷新一个复杂网页 → 再看数字。涨了几十上百是常态——你眼里"一个网页",底下是成百上千个段在跑。
- TCP 为什么要先把数据切成段,而不是一整坨发出去?
- MSS 和 MTU 分别归哪层?1460 这个数是怎么算出来的?
- 三次握手"顺便交换"的东西一共有几件?分别是什么?
- 已知某段序号 2461、长度 1460,下一个段的序号是多少?
- 段 3 丢了,段 4、5、6 都到了,接收方回的 ACK 号是什么?为什么?
- TCP 切段和 IP 分片,最大的区别是什么?
- 序号是 32 位,会不会重复?靠什么解决?
- 序号 1001 是"第 1001 个段"吗?
答案:
① 三堵墙:链路一次装不下(受 MTU 限制)、一整坨坏了得全部重来、一大坨独占线路没法轮流。切成段之后能轮流共用、坏哪段补哪段、大小还刚好合适。
② MTU 归网络接口层(链路体质,以太网 1500);MSS 归传输层(握手协商出来的)。MSS = MTU − IP 头 − TCP 头 = 1500 − 20 − 20 = 1460。
③ 三件:① 我也想连你(建连接)② 我的起始序号(SYN 和 FIN 也各占一个序号)③ 我的 MSS。MSS 取两边较小的那个。
④ 3921。规则是"本段序号 = 上段序号 + 上段长度":2461 + 1460 = 3921。
⑤ 回的还是 ACK 3921。因为累积确认只认缺口的起点——它要交给程序的是连续字节流,3921 那个洞不补上,后面再多也接不上。连说三次就触发了快速重传。注意段 4、5、6 并没白跑,它们在接收缓冲区里存着等拼装。
⑥ TCP 切段在源头主动切,切出来是独立可单独重传的报文段;IP 分片在中途被迫切,切出来是一体同命的分片,丢一片就要整包重来。MSS 的意义就是让 IP 永远不必分片。
⑦ 会重复:2³² 约 4.29 GB 就绕一圈,万兆网络下 3 秒多就绕完。靠时间戳选项 + PAWS 把"上一圈的段"和"这一圈的段"分开。
⑧ 不是。序号是字节号——1001 表示"这一段从第 1001 个字节开始"。所以段 2 的序号会是 2461,差 1460 而不是 1。
九、几条实战规矩
1. 看到"重传率"高,先怀疑 MTU/MSS 不合
链路一变窄(VPN、PPPoE),如果 MSS 没跟着降,TCP 切出来的段就会超重、逼出 IP 分片,重传率随之上升。"连上 VPN 网页就打不开 / 特别慢"多半是这里。
2. 抓包看序号时,记住三条递推关系
- 本段序号 = 上段序号 + 上段长度
- ACK 号 = 对方"下一个期望的字节号"
- SYN、FIN 各占 1 个序号(所以第一个数据字节 = SYN 序号 + 1)
三条对上,一串抓包记录就能读出整段故事。
3. 重复 ACK 是"诊断信号",不是噪声
抓包里连着三个一模一样的 ACK 号,不用等超时——那是在告诉你"这个号开始的数据丢了"。看到它就去查那个位置前后的包。
4. 别用"段数"估流量,要用"字节数"
段数受 MSS 影响(大包少、小包多),同样 1 MB 数据,小包场景下段数会多得多。要估流量看字节计数。
十、常见误区
- ❌ "序号是第几个包" → 是按字节编的号(3.18 已纠,本节再看一次);正因为按字节编,切段长度变化时号码不会乱
- ❌ "MSS 是链路决定的" → 链路给的是 MTU;MSS 是 TCP 握手协商出来的,是"为了迁就 MTU 而倒推的数"
- ❌ "MSS 就是 1460" → 1460 只是 IPv4 + 以太网 的组合值;PPPoE 是 1452、VPN 还要更小
- ❌ "段切得越小越保险" → 头开销占比会上升(每段固定 40 字节头),小包太多反而不划算;正解是"刚好不触发分片"
- ❌ "段 4、5、6 到了,ACK 应该跳到 8300 之后" → 累积确认只认缺口起点,缺 3921 就一直回 3921
- ❌ "ACK 号 X = 收到了 X 号字节" → 是"X 之前的都收到了,从 X 开始还没收到",差一个字节就会判错重传范围
- ❌ "TCP 切段和 IP 分片是一回事" → 源头主动切 vs 中途被迫切;丢了一段补一段,丢了一片全重来
- ❌ "序号永远不会重复" → 32 位约 4.29 GB 就绕一圈,靠时间戳选项 + PAWS 防撞车
- ❌ "SYN、FIN 是控制报文,不占序号" → 各占 1 个序号,所以第一个数据字节号 = SYN 号 + 1
- ❌ "接收方收到的乱序段都丢了" → 不丢,存在接收缓冲区里,缺口一补上就一起交上去——这是"局部重传"能省成本的另一半原因
十一、下一步学什么
- ☑ 3.20 只对传输给应用程序的数据进行分发(UDP) ✅ 已完成 → 只对传输给应用程序的数据进行分发(UDP):本节把切割与可靠性整体抽掉,看"只做分发"的传输层是什么样
- ☐ 3.21(按教材顺序) —— 报个标题接着走:按教材脉络,接下来大概率是 DNS 与域名(把名字换成 IP 地址)、DHCP 的进一步展开,或 第三章小结。等你报标题。
相关笔记
- 只对传输给应用程序的数据进行分发(UDP) —— 下一节:把本节这套"切段、编号、对账"整体抽掉之后,传输层剩下什么——答案是只剩"把数据交给正确的程序"
- 对应用程序的数据进行可靠传输(TCP) —— 上一节:本节的两个主角(序号、确认号)就是它从 TCP 头里挑出来的字段;"快速重传"的触发条件在本节看到了现场
- 接收和传输数据的规则(使用TCP/IP进行通信) —— 握手时"顺便交换"的第三件东西(MSS)藏在这里
- 收发数据时的规则(数据打包) —— MTU 1500 与 IP 分片的出处;本节把"TCP 切段 vs IP 分片"这对概念彻底分开了
- 数据的称谓(信息、数据段、数据包、数据帧) —— "数据段 Segment"就是本节一段段切出来的东西
- 根据端口号分配数据到应用程序(端口号、已知端口号) —— 每段除了序号,还带着源/目的端口,两样一起装在 TCP 头里
- 网络的网络(互联网) —— 千网互联导致各段链路 MTU 不同,这才是"MSS 要取小的那个"的原因
- 数据是否成功传输给接收方(ICMP) —— 对比:ICMP 出问题只能"报信",TCP 靠本节这套序号/ACK 体系能"自己补"
- TCP通信 —— 你
write()一个几百 KB 的数组时,底下正在按本节这套规则切成一段段发出去