TCP/IP 的四层不是「砍掉三层」,是「并掉三层」——活还在,只是没人替你干。
层的边界在代码里也看得见:SO_*、IP_*、TCP_* 三组选项。
1.5.3 讲过 OSI 七层,这一节讲 TCP/IP 自己的四层怎么与它对齐。 这一节会数三组套接字选项常量,那是「层的边界」在代码里的直接证据。
一、四层与七层
教材把 TCP/IP 分成四层,但硬件单列:
| TCP/IP 的层 | 对应 OSI | 典型内容 |
|---|---|---|
| 应用层 | 5、6、7 层 | HTTP、DNS、SMTP |
| 传输层 | 4 层 | TCP、UDP |
| 互联网层 | 3 层 | IP、ICMP、ARP |
| 网络接口层 | 1、2 层 | 以太网、Wi-Fi |
第一行那个「5、6、7 层」就是并入的部分:
会话层(管对话)、表示层(管编码)、应用层(管业务)合并成一层。
所以 TCP/IP 的应用层协议,一个协议要同时管三件事——
HTTP 里既有 Cookie(会话),又有 charset(表示),又有 URL(业务)。
第四行那个「1、2 层」也合并了:TCP/IP 对「用什么线、什么信号」不作规定,
只要能装下 IP 包就行。
这就是它能跑在光纤、双绞线、Wi-Fi、4G 上的原因。
二、层的边界在代码里
三组套接字选项常量,正好对上三层:
| 选项组 | 常量前缀 | 个数 | 管什么 |
|---|---|---|---|
| 套接字通用 | SO_* |
18 | 缓冲区、超时、重用地址 |
| 网络层 | IP_* |
16 | TTL、服务类型、分片 |
| 传输层 | TCP_* |
6 | 不延迟、保活、最大段 |
setsockopt 的第一个参数就是在指定「改哪一层」:
传 SOL_SOCKET 改套接字本身,传 IPPROTO_IP 改 IP 头,传 IPPROTO_TCP 改 TCP 行为。
同一行代码能改三层,靠的就是这个「层编号」。
注意这张表里没有「应用层」的选项组:因为应用层不需要「选项」——它就是你自己写的代码。
这也是「四层够用」的直接体现:协议栈负责下三层,最上面那层是空的,留给应用。
还有一组不在表里的东西值得注意:
TCP_NODELAY 只有 6 个选项里的一个,它管的是「小包要不要立刻发」——
这是 Nagle 算法的开关,第六章会细讲。
现在只需要知道:传输层的选项少,是因为 TCP 的行为大部分不由应用决定。
三、为什么不按七层实现
三个现实理由:
| 理由 | 具体 | 结果 |
|---|---|---|
| 七层太细 | 会话、表示难以独立实现 | 只能并进应用 |
| 四层够用 | 寻址、传输、应用三段清晰 | 实现简单 |
| 先跑起来 | 标准还没定完,实现已部署 | 来不及改 |
第一行是技术原因: 「会话」和「表示」很难做成独立的一层——因为它们的实现高度依赖具体应用。 比如 HTTP 的会话是 Cookie,SMTP 的会话是命令序列,两者没有共同抽象。 第三行是历史原因:TCP/IP 在 OSI 定稿之前就已经跑起来了, 等 OSI 出来,改架构的成本已经太高。 所以「四层」不是「设计出来的最优解」,是「演化出来的结果」。 这也解释了一个常见的困惑:为什么书上讲网络要先讲 OSI 七层,再讲 TCP/IP 四层? 因为 OSI 是「分析工具」,TCP/IP 是「实现」。 分析问题时用七层更清楚,写代码时用四层更直接。
四、动手 Demo:分层在代码里也有边界
数一下 Python 暴露的三组套接字选项常量。 前缀不同,就是层的边界。
import socket
for zh, pre in (('套接字通用', 'SO_'),
('网络层(IP)', 'IP_'),
('传输层(TCP)', 'TCP_')):
names = [x for x in dir(socket) if x.startswith(pre) and x != pre]
print('%-16s %-6s %d 个' % (zh, pre + '*', len(names)))
实跑输出:
--------------------------------------------------------------------
分层在代码里也有边界
--------------------------------------------------------------------
**「TCP/IP 与 OSI 怎么对应」这个问题,在代码里能找到直接证据。**
下面数一下 Python 暴露的三组套接字选项常量。
选项组 常量前缀 个数
------------------------------------
套接字通用 SO_* 18
网络层(IP) IP_* 16
传输层(TCP) TCP_* 6
**三组常量的前缀不一样,这就是层的边界**:**`SO_*` 是「套接字本身」的选项**(缓冲区、超时、
重用地址);**`IP_*` 是网络层的**(TTL、服务类型);**`TCP_*` 是传输层的**(不延迟、保活)。
**`setsockopt` 的第一个参数就是在指定「改哪一层」**:
**传 `SOL_SOCKET` 改套接字,传 `IPPROTO_IP` 改 IP 头,传 `IPPROTO_TCP` 改 TCP 行为。**
**同一行代码能改三层,靠的就是这个「层编号」。**
**这也解释了为什么四层模型够用**:
**OSI 把「表示」「会话」单列,但在代码里它们没有独立的选项组**——**这两件事得应用自己处理。**
三组分别是 18、16、6 个:
套接字通用的最多,因为它管的是「文件描述符这一层」的行为(缓冲区、超时);
TCP 的最少,只有 6 个,因为 TCP 的大部分行为不由应用控制。
setsockopt 的签名是 (level, optname, value):第一个参数 level 就是这三个常量之一,
它决定「改哪一层」。
所以「分层」在代码里不是抽象概念,是一个必填参数。
这也解释了为什么四层模型够用:
OSI 把「表示」「会话」单列,但在代码里它们没有独立的选项组——这两件事得应用自己处理。
30 秒实验
- 试着
setsockopt(SOL_SOCKET, SO_RCVBUF, ...)改接收缓冲区,看能不能设成很大。 - 数一下
socket模块里以IPPROTO_开头的常量有几个。 - 想一下:如果 OSI 的七层都做成独立选项组,会有哪几组?
常见误区
- 以为四层是「砍掉三层」。 是并掉:会话、表示并入应用,物理并入网络接口。 活还在,只是没人替你干。
- 以为四层是「最优设计」。 它是演化结果:先跑起来、后定标准,改不动了就这样了。
- 以为分层只是教学工具。 它在代码里是必填参数:
setsockopt的第一个参数就是「哪一层」。
自测三问(点开对答案)
1. TCP/IP 的四层分别对应 OSI 的哪几层? —— **应用层对应 5、6、7 层,传输层对应 4 层,互联网层对应 3 层,网络接口层对应 1、2 层。** 2. 为什么 TCP/IP 不按七层实现? —— **会话与表示难以独立抽象**(HTTP 用 Cookie、SMTP 用命令序列,没有共同点), 而且标准没定完实现已部署,改不动了。 3. 「层的边界」在代码里怎么体现? —— **`setsockopt` 的第一个参数就是层的编号**:`SOL_SOCKET`、`IPPROTO_IP`、 `IPPROTO_TCP` 分别对应三层。下一节往下走一层:硬件,也就是物理层。
- ☐ (本节完,下一节 2.4.2 硬件(物理层))