📚 第二章 TCP/IP基础知识
📄 一句话概括

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 秒实验

  1. 查一下 esp 与 ah 的协议号,看它们占了哪两个编号。
  2. 数一下:TCP 头 20 字节 + IP 头 20 字节 = 40 字节, 这 40 字节里有多少是「地址」、多少是「可靠性」?
  3. 想一下:如果只允许一个传输层协议,视频会议会变成什么样?

常见误区

  1. 以为 UDP 是「TCP 的简化版」。 它们是同一层的两个并列选择,在 IP 头里各占一个协议号。
  2. 以为 TCP/IP 是一个协议。 它是一族协议,IP 头里那个「协议」字段就是用来区分上层的。
  3. 以为拆开是为了「分层好看」。 拆开是为了「能单独替换」,UDP、SCTP、 ESP 全都是替换的产物。
自测三问(点开对答案) 1. TCP/IP 为什么必须是两个协议? —— **因为「跨网寻址」和「可靠传输」是两件独立的事**:寻址要盖住不同网络的差异, 可靠性只有部分应用需要。 2. 怎么从字段名看出 TCP 与 IP 的分工? —— **TCP 头里没有一个地址,IP 头里没有一个端口。**TCP 管「哪个程序、第几个字节」, IP 管「从哪台机器、到哪台机器」。 3. IP 头里那个「协议」字段有什么可扩展性? —— **8 位,最多 256 种上层协议。**加新传输层协议只需在 IP 头里占一个新编号,不用改 IP 头结构。

下一节讲这套协议怎么扩散开的:UNIX 与 BSD。

相关笔记