📚 第一章 网络基础知识
📄 一句话概括

分层的操作定义只有一句话:把上一层的整包当成自己的数据,在前面加自己的头。 分层买到的不是性能,是「换一层不影响其他层」的自由。

📝 本节和前面几节的关系

1.4 讲完了标准由谁定,1.5 进入本书最核心的一张图:分层。 这一节先讲「分层到底是怎么做的」,1.5.2 用对话打比方,1.5.3 才给 OSI 七层。

一、不分层会怎样

假设只有一个协议管所有事:

问题 具体表现
改一处全崩 换个网卡,上层代码全要重写
没法复用 换个应用,底下全要重做
没法分工 一个人得懂编码、路由、电气

第一行是最致命的: 不分层的话,「换网卡」和「换浏览器」是同一件事——因为它们在同一段代码里。 分层之后,换网卡只动最底下那层,上面一行不改。 第三行说的是人的问题,不只是代码的问题:分层把「懂光纤的人」和「懂网页的人」隔开, 让他们能各自深入,不需要互相懂。 这是分层最被低估的收益。

二、分层的操作定义

不用记图,记住这一个动作就够了:

应用层   [ HTTP 报文 ]
传输层   [ TCP 头 ][ TCP 头后面的整包 ]
网络层   [ IP 头  ][ 上层整包 ]
链路层   [ 以太网头 ][ 上层整包 ]

用一条 27 字节的 HTTP 报文走一遍:

层 本层名字 本层加的头 累计长度
应用层 HTTP 报文 — 27
传输层 TCP 段 20 47
网络层 IP 包 20 67
链路层 以太网帧 14 81

注意最后一列一直在变大、第二列一直在变:同一份数据,在每一层都有个新名字、新长度。 名字不是修辞,是「此刻这一层的视角」。 接收端反过来做:剥掉以太网头、剥掉 IP 头、剥掉 TCP 头,最后交上去的还是那 27 字节。 一个字节都没多、没少——这就是「封装」与「解封装」的严格对称。 头部是空的(全 0)不影响这个演示:这里只看长度,不看内容。 真实头部每个字段都有含义,第四章和第六章会逐个拆开。

三、分层的代价

分层不是白拿的,三处代价:

代价 来源 量级
多一次拷贝 每层都要组装/拆开 中
多一段头部 每层加自己的头 14+20+20=54 字节
多一次查表 每层各查各的表 小

第二行那个 54 字节是最容易被低估的: 一份 27 字节的请求,实际在线上要走 81 字节——头是正文的两倍。 这就是为什么小请求的传输效率天然很低。 第一行是高性能场景的老问题:一层套一层就意味着多次内存拷贝, 所以才会出现「零拷贝」「旁路」这些词——它们的本质都是「把中间几层省掉」。 但要注意:绕过分层是有代价的。 省掉的那几层,也就省掉了它们提供的检查与适配,所以这类方案只在自己完全可控的机房内网里用。

四、动手 Demo:分层就是一层套一层

手工构造四个头部,一层一层套上去,数累计长度。 这是本节那张表的代码版,数字完全对得上。

import _netsim as N

app = b'GET / HTTP/1.0\r\n\r\n'.ljust(27, b'x')[:27]
seg = N.tcp4h(40000, 80, flags=0x18) + app
pkt = N.ip4h('192.168.3.152', '93.184.216.34', seg, proto=6)
frm = N.eth('ff:ff:ff:ff:ff:ff', '9c:b6:d0:0d:d5:0f', pkt)

for name, buf, own in (('应用层 HTTP 报文', app, 0),
                        ('传输层 TCP 段', seg, N.TCP_HDR),
                        ('网络层 IP 包', pkt, N.IP_HDR),
                        ('链路层 以太网帧', frm, N.ETH_HDR)):
    print('%-16s 本层加 %-3d 累计 %-4d' % (name, own, len(buf)))
print('剥回去:%d' % len(frm[N.ETH_HDR + N.IP_HDR + N.TCP_HDR:]))

实跑输出:

--------------------------------------------------------------------
分层就是一层套一层
--------------------------------------------------------------------
**协议分层的操作定义很简单:每一层把上一层的整包当成自己的「数据」,在前面加自己的头。**

  层         这一层的名字     本层加的头     本层总长
  ----------------------------------------------------
  应用层     HTTP 报文        27             27
  传输层     TCP 段           20             47
  网络层     IP 包            20             67
  链路层     以太网帧         14             81

**注意最后一列一直在变大,第二列一直在变**:**同一份数据,在每一层有个新名字、有个新长度。**

**接收端反过来做**:剥掉以太网头、剥掉 IP 头、剥掉 TCP 头,
**最后交上去的还是那 27 字节**——一个字节都没多、没少。

27、47、67、81 这四个数和书上的表完全一致:因为头部长度就是 20、20、14,一个不多一个不少。 最后一行剥回去还是 27 字节,这就验证了「封装与解封装严格对称」。 tcp4h 里算的校验和是真的按 RFC 1071 算的,所以这些头部不是随手填的 0, 换一下端口号,校验和就会跟着变。

30 秒实验

  1. 把 app 改成 100 字节,看四层的累计长度各是多少。
  2. 把 eth 的 etype 改成 0x86DD(IPv6),想想这时候 81 这个数会变成多少。
  3. 算一下:如果去掉分层、只用一个头,理论上最少要多少字节才能把「从哪来、到哪去、 给哪个程序」都说清楚。

常见误区

  1. 以为分层是为了「好看、好画图」。 它是为了「可替换」:换一层不影响其他层, 这才是分层的价值。
  2. 以为分层没有成本。 每层 14~20 字节的头是实打实的开销,小请求里头能占到一半以上。
  3. 以为「零拷贝」是免费的优化。 它是绕过分层,省掉的同时也丢掉了那几层的检查与适配, 只能在可控环境里用。
自测三问(点开对答案) 1. 用一句话说清「分层」到底做了什么? —— **每一层把上一层的整包当成自己的「数据」,在前面加自己的头。**接收端反着剥。 27→47→67→81 就是这么来的。 2. 分层的最大收益是什么? —— **可替换**:换网卡只动链路层,换浏览器只动应用层,中间的层一行不改。 此外还有「让人能分工深入」这个常被忽略的收益。 3. 一份 27 字节的 HTTP 请求,在以太网上实际要走多少字节? —— **81 字节**:加 TCP 头 20、IP 头 20、以太网头 14。 **头是正文的两倍**,这就是小请求效率低的根本原因。

下一节换个说法讲分层:用「两个公司的部门之间传话」这个比方。

相关笔记