本文是计算机网络专题的阶段 1。阶段 0 沿着一次地址栏输入走完整条链路, 指认出每一站属于哪一层。本篇换一个方向:假设这些层都还不存在,从零把它们一层层搭出来。
分层模型通常是被直接给出的:七层背下来,四层记住对应关系,然后开始学各层协议。 这个顺序有个问题——层次是结论,而学习者拿到的只有结论。
下面把推导过程走一遍。场景很小:两台机器,一根网线,要把一句 hello 送到对面。
推进方式固定为三步:这一层解决了什么,它解决不了什么,因此下一层是什么。
上图是接下来五节的路线预览。现在从最底下开始。
第 1 层 · 物理层:让 0 和 1 能被认出来
手上只有一根线。发送方能做的事只有一件:改变线上的电压。
问题在于接收方感知到的是连续变化的电平,而不是 0 和 1。 多高的电压算 1、一个比特要维持多长时间、线的两端插头形状是否匹配—— 这些都没有约定,收到的就只是一串无法解释的波形。
办法是把这些约定固化下来:电压范围、时序、接口与介质规格。 双方按同一份规格收发,比特就能穿过线缆。这一层不关心比特的含义, 它只保证「发出去的 1 到对面还是 1」。
第 2 层 · 数据链路层:把比特切成帧,并区分设备
比特能过去了,但接收方拿到的是一条不间断的流。 没有边界,就无法判断一段数据从哪开始、到哪结束; 更麻烦的是干扰会翻转其中几位,而接收方完全察觉不到。
办法是成帧:把比特切成有头有尾的块,块尾附上一段由内容算出的校验值。 接收方重新计算一遍,对不上就丢弃整帧。数据从此有了边界,也有了完整性检查。
真实网络里很快出现第二个变化:一条线上不止两台机器。 所有设备都能听到全部信号,收到帧的机器需要判断这一帧是不是发给自己的。 于是每块网卡被分配一个唯一编号——MAC 地址(Media Access Control Address), 帧头写明收发双方,不是自己的就忽略。
到这里,同一段链路内部的通信已经完全可用。问题出在离开这段链路之后: MAC 地址由厂商在生产时烧录,与设备所在的位置没有任何关系。 从一个 MAC 地址看不出对方在哪座城市、在哪个网络,中间设备也就无从判断该往哪个方向转发。
第 3 层 · 网络层:跨网络寻址与逐跳接力
跨网络通信需要一套按位置分配的地址。这就是 IP 地址: 它不是设备的固有属性,而是接入某个网络时获得的位置标识,因此可以从中判断方向。
有了位置标识,转发规则也随之确定:中间设备只读 IP 头里的目的地址, 查自己的路由表选出下一跳,把包交过去,然后忘掉这件事。 每经过一跳,外层的帧头被拆掉按下一跳重写,而 IP 头基本保持不变。 这种「只管下一跳、不管全程」的接力,让互联网可以由无数自治的网络拼起来。
代价是这一层的承诺被压到最低:尽力而为。 包可能因为队列满而被丢弃,可能因为走了不同路径而乱序到达,也可能重复。 网络层不做纠正,也不通知任何人。
还有一个它管不到的问题:包送到了机器,但机器上同时运行着浏览器、邮件和终端, IP 地址只标识机器,无法说明这个包属于哪个程序。
第 4 层 · 传输层:交给哪个程序,以及把丢掉的补回来
两个遗留问题都靠加一层编号解决。
端口号解决归属:每个程序占用一个编号,包头写明源端口与目的端口, 到达后按端口交付。同一台机器上的多个程序因此互不干扰。
序列号与确认解决可靠性:发送方给每个字节编号,接收方确认收到了哪些, 缺失的部分由发送方重发,乱序到达的部分按编号重排。 在此基础上还要控制发送速度——既不能超过接收方的处理能力,也不能超过网络的承载能力, 后者就是拥塞控制。
这些机制合起来,把网络层的「尽力而为」变成了应用可以直接使用的有序、完整的字节流。 需要注意的是,可靠不是必须的:实时音视频宁可丢一帧也不愿等待重传, 因此传输层同时提供了不做这些工作的 UDP。是否要可靠,由应用自己选。
字节流交到应用手上,最后一个问题浮现:字节流本身是中性的。 一端发来一串字节,另一端不知道它是请求还是响应、要的是哪份资源。
第 5 层 · 应用层:让字节有含义
最后一层不解决传输问题,只解决理解问题:约定报文的格式与语义。
以 HTTP 为例,它规定第一行写方法、路径与版本,其后每行是一个头部字段, 空行之后是消息体;响应则以状态码开头。双方按同一套规则解析同一串字节, 才谈得上「请求成功」或「资源不存在」。
至此链条闭合。再往上的问题——内容是否正确、权限是否足够、业务规则是否满足—— 已经不属于网络,而属于应用自己。
回头看:五层如何配合
五层各守一个问题,配合方式由三个约定确定,它们指的是不同方向:
- 服务:每一层向上层提供的能力。传输层对应用层的承诺是「把这段数据完整有序地交给对端进程」,至于中途重传了几次,应用层看不到,也不需要知道。
- 接口:相邻两层之间的调用方式。写代码时用到的套接字(socket)就是应用层与传输层之间的接口。
- 协议:通信双方同一层次之间的约定。TCP 是两端传输层之间的协议,中间的路由器不参与,也不需要理解。
第三条最容易产生误解:同层之间看起来在直接对话,实际上它们从不直接相连。
水平虚线是逻辑关系,移动的方块才是数据的真实路径。 浏览器的 HTTP 与服务器的 HTTP 在谈同一份资源,但这段请求必须先交给本机的传输层, 再交给网络层、链路层,变成信号送出去,由对端逐层上交,才能到达对面的 HTTP。 任何一层想跟对面的同层说话,都只能麻烦自己的下层。
数据下行时每经过一层就被加上一段头部,这个过程叫封装,上行时逐层剥离,叫解封装。 其中的关键规则是:下层不解释上层的内容。上层交下来的整个单元被当作不透明的载荷处理, 就像邮局只看信封上的地址,不会拆开信纸读内容。 正因如此,同一段 HTTP 请求既可以装进 TCP,也可以装进 QUIC,传输层不需要知道里面是什么。
各层的数据单元与寻址方式也各不相同:
| 层 | 数据单元 | 寻址依据 | 定位的对象 |
|---|---|---|---|
| 应用层 | 报文(message) | URL、域名 | 一份资源 |
| 传输层 | 段(segment)、数据报(datagram) | 端口号 | 主机上的一个进程 |
| 网络层 | 包(packet) | IP 地址 | 互联网上的一台主机 |
| 链路层 | 帧(frame) | MAC 地址 | 同一链路上的一台设备 |
这四种地址不是层层包含的关系,而是各答一个问题,在一次通信中同时生效: IP 地址决定整程去哪,MAC 地址决定这一跳交给谁,端口号决定到达后交给哪个程序。
为什么不把五层合成一个协议
既然最终目的只是把 hello 送到对面,一个从电信号管到报文格式的大协议似乎更省事。
这条路走不通,原因可以算出来。
设有 n 种应用协议与 m 种链路技术。不分层时,每种应用都要为每种链路各写一份实现, 需要 n × m 套;分层之后,应用只对接 IP,链路也只对接 IP,需要 n + m 套。 六种应用与六种链路是 36 对 12,而真实互联网的两侧都是成百上千种。
比总量更关键的是增量成本。不分层时新增一种链路技术,所有既有应用都要跟着改一遍; 分层后只要这种链路能承载 IP 包,全部既有应用不改一行就能在上面运行。 无线网络在 1990 年代接入互联网时,没有任何一个应用协议为此修改过——这是分层的直接结果。
这个结构常被称作沙漏模型:上方的应用协议种类繁多,下方的链路技术同样繁多, 中间收窄成 IP 一种。收窄点之所以能是 IP,恰恰在于第 3 层承诺得足够少: 只负责尽力把包送到目的地址。承诺越少,能实现它的链路就越多,能接受它的应用也越多。
与教科书上的两套模型对照
前面搭出的五层,与两套标准模型基本吻合,只是切法略有差别。
OSI 参考模型(ISO/IEC 7498-1)分七层,是国际标准化组织给出的完整抽象; TCP/IP 模型分四层,由 RFC 1122 描述,是实际部署的结构。 两者不是竞争关系:OSI 提供讨论问题用的坐标系,TCP/IP 提供真正运行的代码结构。
差异集中在两端。上三层里,表示层与会话层没有形成独立的协议: 编码转换、压缩、会话恢复这些工作高度依赖具体业务,抽成通用层收益很小, 最终由各应用协议、序列化库与 TLS 会话票据分头实现。 下两层则相反,OSI 把物理与链路分开,TCP/IP 直接交给各种链路技术自行规定, 因为以太网、Wi-Fi、蜂窝网各有一整套成熟标准,没有必要在模型里重复一遍。
TLS 的位置是这套划分留下的缺口:它在传输层之上、应用层之下, 标准层次里没有它的位置,因此常被称作 4.5 层。QUIC 索性不再区分, 把 TLS 握手嵌进传输协议内部,两者不可分割。
至于七层要不要背——它的用处不是照着写代码,而是作为共同的沟通语言。 说「二层不通」「四层转发」「七层网关」时,双方能立刻对齐指的是哪一段职责。
这套结构的账单
收益已经清楚,成本同样需要算。第一项可以精确到字节:每层的头部都要占带宽,且是固定成本。
以太网帧头与帧尾 18 字节、IP 头 20 字节、TCP 头 20 字节,合计 58 字节。 传输 1460 字节的满载数据时,这 58 字节只占 3.7%,完全可以接受; 但在交互式场景里,一次按键只有 1 字节数据,同样要付 58 字节的头部, 有效载荷率跌到 2% 以下。这正是 Nagle 算法要把小包攒起来再发的原因。
这里牵出两个常用量:链路单帧能承载的上限称为最大传输单元(Maximum Transmission Unit,MTU), 以太网通常是 1500 字节;扣掉 IP 头与 TCP 头后留给数据的上限称为最大段长 (Maximum Segment Size,MSS),典型值 1460 字节。 隧道会让这笔账更紧张:VPN 把一个完整的 IP 包再装进另一个 IP 包,外层头部又占掉几十字节。 若两端没有正确协商,表现出来的现象往往是「小请求正常,大文件卡住」—— 包超过路径上某段的 MTU 后被丢弃,而携带该消息的 ICMP 报文又常被防火墙拦下。 路径 MTU 发现(RFC 1191)正是为这种情形设计的。
第二项成本无法量化,影响却更深:隐藏实现既是分层的价值,也是它的问题来源。
典型例子是无线网络上的 TCP。TCP 的拥塞控制假设丢包意味着网络拥塞,于是丢包时大幅降速。 这个假设在有线网络里成立,在无线网络里却不一定:信号干扰造成的丢包与拥塞无关, 此时降速既不能改善信号,还白白损失了吞吐。传输层看不到「这是无线链路,刚才是干扰」, 只能按唯一可见的信号——丢包——做判断。
解决办法不是取消分层,而是在接口上开一条明确的通道,把必要的信息放行:
- 显式拥塞通知(Explicit Congestion Notification,ECN,RFC 3168)让路由器在 IP 头里标记拥塞,接收方回传给发送方。网络层由此可以在丢包发生之前通知传输层。
- MSS 协商让传输层在握手时就知道链路的长度限制,避免发出注定被分片的段。
- 路径 MTU 发现让端点探测出整条路径上最小的 MTU。
它们的共同点是:信息以受控、明确的方式跨层传递,而不是让上层去猜下层的状态。 这是分层原则在实践中正确的松动方式。
当边界被打破
理论上,路径中间的设备只应该看 IP 头。现实中并非如此。
NAT 要改写端口号,防火墙要按端口过滤,负载均衡器要读 TCP 状态, 运营商设备可能重写 TCP 选项。这些设备统称中间盒(middlebox,RFC 3234 对其做了系统记录), 它们工作在本不该触及的层次上,并且只认识部署当时的报文格式。
后果是协议僵化:传输层的任何新特性都可能被中间设备当作异常丢弃。 TCP Fast Open(RFC 7413)与多路径 TCP(RFC 8684)都遇到过这类问题—— 协议本身没有缺陷,但在真实网络上无法可靠部署,TCP 的演进因此几乎停滞。
QUIC 的应对方式值得说清楚:它把传输层头部的大部分加密,只留下 UDP 头与少量必要字段可见。 这里加密的目的不是保密,而是保住演进空间——中间设备读不到,也就无法据此判断和改写, 协议的内部格式于是重新变得可以修改。这是用密码学手段重新划定分层边界。
判断依据来自 1984 年的端到端论证:如果某项功能必须由端点参与才能确认完成, 那么把它放进网络内部并不能免除端点的工作,至多只能作为性能优化。 可靠传输、加密、身份认证都属于这一类,因此它们的正确位置在边缘,网络核心只负责转发。
这套模型怎么用
最后需要澄清:四层或七层都是描述工具,不是必须遵守的层数。
隧道就是明证。VXLAN 把以太网帧装进 UDP,WireGuard 把 IP 包装进 UDP, IP over IP 把 IP 包装进 IP 包。此时栈的层数变成六层甚至更多,同一种协议在不同位置出现两次。 分层描述的是职责的划分方式,不是固定的层数清单。
日常工作中,这套模型最实用的价值是排障顺序,而顺序恰好就是本文搭建的顺序: 链路层是否通、网络层能否到达、传输层端口是否开放、应用层返回了什么。 每确认一层就排除掉一批可能,比在应用层反复猜测有效得多。
常见问题
下一站
阶段 2 回到第 2 层:以太网帧的具体结构、MAC 地址的分配方式、 ARP 如何找到下一跳的网卡,以及交换机凭什么知道该把帧从哪个端口发出去。 本篇留下的一个问题也会在那里得到答案——既然链路层已经有校验,传输层为什么还要再校验一次。
参考资料
- RFC 675 · Specification of Internet Transmission Control Program
- RFC 791 · Internet Protocol
- RFC 1122 · Requirements for Internet Hosts
- RFC 1191 · Path MTU Discovery
- RFC 3168 · The Addition of Explicit Congestion Notification (ECN) to IP
- RFC 3234 · Middleboxes: Taxonomy and Issues
- RFC 7413 · TCP Fast Open
- RFC 8684 · TCP Extensions for Multipath Operation
- RFC 9000 · QUIC: A UDP-Based Multiplexed and Secure Transport
- ISO/IEC 7498-1 · Open Systems Interconnection — Basic Reference Model
- Saltzer, Reed, Clark · End-to-End Arguments in System Design