TCP/IP 之所以是「两个协议」,是因为「可靠传输」和「跨网寻址」是两件事。 拆开之后,两者都能被单独替换——UDP 就是「只换掉 TCP」的产物。
2.1.2 讲的是网络,这一节讲协议本身的设计。 这一节的证据是「字段名」:看两个头部各有什么,就知道分工在哪。
一、一个协议装不下
1974 年那篇论文要解决的问题有两个:
| 要解决 | 难点 | 如果塞进一个协议 |
|---|---|---|
| 跨网寻址 | 不同网络地址格式不同 | 每加一种网就要改协议 |
| 可靠传输 | 丢包、乱序、重传 | 不需要可靠的场景也得付代价 |
第一行是「网际」的核心难点:卫星网、无线电网、地面线的寻址方式完全不同, 如果协议里写死了某一种地址格式,就永远只能连一种网。 所以必须有一层「统一的地址」,把底下的差异盖住——这就是 IP 层。 第二行是「拆开」的另一个理由:不是所有应用都需要可靠传输。 语音、视频宁可丢包也不要等重传,如果只有一个「可靠版」协议,这些应用就得白付重传的代价。 所以必须有一个「不可靠版」可选——这就是 UDP。
二、看字段名就明白分工
把两个头部的字段摆在一起,分工一目了然:
| TCP 头里的字段 | IP 头里的字段 |
|---|---|
| 源端口 | 版本 |
| 目的端口 | 首部长度 |
| 序号 | 总长度 |
| 确认号 | 标识 |
| 标志位 | 生存时间 |
| 窗口 | 协议 |
| 校验和 | 源地址 |
| 紧急指针 | 目的地址 |
左列九个字段里没有一个地址,右列表里没有一个端口。 这不是巧合,是刻意分开的。 TCP 只管「哪个程序、第几个字节、收到没有」;IP 只管「从哪台机器、到哪台机器」。 所以换掉 TCP 不影响寻址,换掉 IP 不影响可靠性。 这个分工还能再往下拆一层: 右列里那个「协议」字段只有 8 位,它记录「上面装的是谁」——6 是 TCP、17 是 UDP、1 是 ICMP。 一个字节,就让 IP 层能承载任意多种上层协议,不需要为每种协议改 IP 头。
三、拆开之后能替换
同一层可以有好几种实现,这才是拆开的收益:
| 协议 | 头部字节 | 换来什么 |
|---|---|---|
| TCP | 20 | 可靠、有序、有流量控制 |
| UDP | 8 | 不建连、开销小、保边界 |
| SCTP | 12 | 多流、多宿主 |
| ESP/AH | 不定 | 加密与认证 |
第二行是「替换」最成功的例子:UDP 就是把 TCP 换掉、只留端口概念的结果, 头部从 20 字节减到 8 字节。 它在 IP 头里占的是另一个协议号(17),与 TCP 并列。 第三、四行说明这个位置能装更多东西:SCTP 用于电信信令,ESP/AH 用于 IPsec, 它们都不改 IP 头,只在「协议」字段里占一个新的编号。 这就是「一个字节的可扩展性」——8 位最多 256 种,够用几十年。 对比一下:如果当年把 TCP 与 IP 合成一个协议,今天想加一个「不可靠版」就得改全网的实现。 所以「拆得对不对」的标准不是「优雅」,是「以后好不好改」。
四、动手 Demo:TCP 与 IP 分开,是结构上的分工
把两个头部的字段名摆出来对照,再看本机的协议号。 重点是「左列没有地址、右列没有端口」。
import socket
tcp = ['源端口', '目的端口', '序号', '确认号', '数据偏移',
'标志位', '窗口', '校验和', '紧急指针']
ip = ['版本', '首部长度', '区分服务', '总长度', '标识',
'标志与片偏移', '生存时间', '协议', '首部校验和',
'源地址', '目的地址']
for i in range(max(len(tcp), len(ip))):
print('%-14s %s' % (tcp[i] if i < len(tcp) else '—',
ip[i] if i < len(ip) else '—'))
print('TCP 协议号 %d' % socket.getprotobyname('tcp'))
print('UDP 协议号 %d' % socket.getprotobyname('udp'))
实跑输出:
--------------------------------------------------------------------
TCP 与 IP 分开,是结构上的分工
--------------------------------------------------------------------
**「TCP/IP 为什么拆成两个协议」这个问题,看字段名就明白了。**下面把两个头部的字段名摆出来对照。
TCP 头里的字段 IP 头里的字段
---------------------------------
源端口 版本
目的端口 首部长度
序号 区分服务
确认号 总长度
数据偏移 标识
标志位 标志与片偏移
窗口 生存时间
校验和 协议
紧急指针 首部校验和
— 源地址
— 目的地址
**左列九个字段里没有一个地址,右列表里没有一个端口**——**这不是巧合,是刻意分开的。**
**TCP 只管「哪个程序、第几个字节、收到没有」;IP 只管「从哪台机器、到哪台机器」。**
**所以换掉 TCP 不影响寻址,换掉 IP 不影响可靠性**——
**UDP 就是「只换掉 TCP」的产物:头部从 20 字节减到 8 字节。**
**本机协议表里 TCP 是 6 号、UDP 是 17 号**——
**两者在 IP 头里各占一个编号,是并列关系,不是上下关系。**
左列 9 个字段、右列 11 个字段,加起来正好是 20 字节与 20 字节: TCP 头最短 20 字节,IP 头最短也是 20 字节。 本机协议表里 TCP 是 6 号、UDP 是 17 号: 两者在 IP 头里各占一个编号,是并列关系,不是上下关系。 这一点最容易搞混——很多人以为「UDP 是 TCP 的简化版」,其实它们是同一层的两个不同选择。 右列那个「协议」字段只有 1 个字节,却能区分 256 种上层协议: 这就是为什么加新传输层协议不用改 IP 头。 拆开的价值,最终落在「一个字节的扩展位」上。
30 秒实验
- 查一下
esp与ah的协议号,看它们占了哪两个编号。 - 数一下:TCP 头 20 字节 + IP 头 20 字节 = 40 字节, 这 40 字节里有多少是「地址」、多少是「可靠性」?
- 想一下:如果只允许一个传输层协议,视频会议会变成什么样?
常见误区
- 以为 UDP 是「TCP 的简化版」。 它们是同一层的两个并列选择,在 IP 头里各占一个协议号。
- 以为 TCP/IP 是一个协议。 它是一族协议,IP 头里那个「协议」字段就是用来区分上层的。
- 以为拆开是为了「分层好看」。 拆开是为了「能单独替换」,UDP、SCTP、 ESP 全都是替换的产物。
自测三问(点开对答案)
1. TCP/IP 为什么必须是两个协议? —— **因为「跨网寻址」和「可靠传输」是两件独立的事**:寻址要盖住不同网络的差异, 可靠性只有部分应用需要。 2. 怎么从字段名看出 TCP 与 IP 的分工? —— **TCP 头里没有一个地址,IP 头里没有一个端口。**TCP 管「哪个程序、第几个字节」, IP 管「从哪台机器、到哪台机器」。 3. IP 头里那个「协议」字段有什么可扩展性? —— **8 位,最多 256 种上层协议。**加新传输层协议只需在 IP 头里占一个新编号,不用改 IP 头结构。下一节讲这套协议怎么扩散开的:UNIX 与 BSD。
- ☐ (本节完,下一节 2.1.4 UNIX系统的普及与互联网的扩张)