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

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

  1. 试着 setsockopt(SOL_SOCKET, SO_RCVBUF, ...) 改接收缓冲区,看能不能设成很大。
  2. 数一下 socket 模块里以 IPPROTO_ 开头的常量有几个。
  3. 想一下:如果 OSI 的七层都做成独立选项组,会有哪几组?

常见误区

  1. 以为四层是「砍掉三层」。 是并掉:会话、表示并入应用,物理并入网络接口。 活还在,只是没人替你干。
  2. 以为四层是「最优设计」。 它是演化结果:先跑起来、后定标准,改不动了就这样了。
  3. 以为分层只是教学工具。 它在代码里是必填参数: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` 分别对应三层。

下一节往下走一层:硬件,也就是物理层。

相关笔记