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

标准化统一的是「行为」,不是「写法」——同一件事可以有好几种合规的实现。 标准文档里的 MUST/SHOULD/MAY 三档,决定了「不合规」到底有多严重。

📝 本节和前面几节的关系

1.4.1 讲了谁在定标准,这一节讲「标准定下来之后管什么、不管什么」。 这一节会用到 HTTP/1.1 与 HTTP/2 的对比,1.5 之后会从分层角度再讲一次。

一、标准统一了什么

标准管的是「对端能看到的行为」,不管「内部怎么实现」:

对象 标准管不管 例子
报文格式 管 请求行三段、CRLF 结尾
状态码语义 管 404 就是「找不到」
内部数据结构 不管 用哈希表还是红黑树随便
编程语言 不管 C 写的和 Go 写的能互通

第三、四行是标准化的分寸感所在:只要对外行为一致,里面怎么折腾都行。 正因为不管内部,各家才能在自己的场景里优化—— nginx 和 Apache 对同一个请求回一样的状态码,但内部结构完全不同。 第一行那个「CRLF 结尾」是典型的「必须精确」的例子:差一个字节就可能被拒。 而「状态码语义」是「意思要对」的例子:你可以加自己的原因短语,但 404 的意思不能改。

二、三档要求

RFC 里的关键词是有法律效力级别的(RFC 2119 定义):

英文 中文 违反的后果
MUST 必须 就是不合规,别人可以不兼容你
SHOULD 应当 有正当理由可以不做
MAY 可以 做不做都合规

第一行解释了为什么很多「小毛病」会导致连不上:比如 HTTP/1.1 的 Host 是 MUST, 不发就是不合规,服务端回 400 完全正当。 第二行是最容易被忽略的一档:「应当」意味着「不做要能说出理由」, 所以有些老实现为了兼容,会故意违反 SHOULD——这不是 bug,是权衡,但得写进文档。 读协议文档时先找这三个词,它们标出了「硬边界」与「软建议」的分界, 比通读全文效率高得多。

三、标准会被换掉

同一组语义,可以有两种完全不同的写法:

| 版本 | 头部编码 | 四个头的字节数 | |---|---| | HTTP/1.1 | 纯文本,一行一个 | 65 | | HTTP/2 | 二进制 + 静态表索引 | 4 |

从 65 字节压到 4 字节,靠的是「双方都认得同一张表」: GET、https、/、example.com 这些最常见的内容,在静态表里有固定编号,直接发编号就行。 注意这里「语义一个字没改」:请求还是请求,头还是头,404 还是 404。 改的只是编码方式——这正是标准化的好处:换编码不用换应用。 所以升到 HTTP/2 不需要改业务代码,这也是它能推得动的原因。 反过来,任何要求「应用层跟着大改」的协议升级,基本都推不动。

四、动手 Demo:标准化之后,同一件事有两种写法

把同一组 HTTP 头部,分别按 1.1 的纯文本和 2 的静态表索引编码。 数一下字节差多少。

hdrs = [(':method', 'GET'), (':scheme', 'https'),
        (':path', '/'), (':authority', 'example.com')]

text = ''.join('%s: %s\r\n' % h for h in hdrs)
print('HTTP/1.1 纯文本 %d 字节' % len(text.encode('latin-1')))

# RFC 7541 附录 A 的静态表里,这四项的索引是固定的
STATIC = {(':authority', ''): 1, (':method', 'GET'): 2,
          (':path', '/'): 4, (':scheme', 'https'): 7}
tot = 0
for k, v in hdrs:
    i = STATIC.get((k, v), 0)
    n = 1 if i else len(('%s: %s' % (k, v)).encode()) + 1
    tot += n
    print('%-12s 索引 %-3d 编码 %d 字节' % (k, i, n))
print('HTTP/2 编码后共 %d 字节' % tot)

实跑输出:

--------------------------------------------------------------------
标准化之后,同一件事有两种写法
--------------------------------------------------------------------
**HTTP/1.1 的头部是纯文本,HTTP/2 改成了二进制加索引表。**同一组头部,两种写法的字节数差得很远。

HTTP/1.1 写法(纯文本,一行一个头)共 65 字节:

    :method: GET
    :scheme: https
    :path: /
    :authority: example.com

HTTP/2 用 HPACK 静态表,前四项都有固定索引:

  头部              静态表索引     编码后字节
  --------------------------------------------
  :authority        1              1
  :method GET       2              1
  :path /           4              1
  :scheme https     7              1

**四个头从 65 字节压到 4 字节**(每项一个索引字节,最高位为 1 表示「全索引」)。

**这就是「标准化」的另一面**:**标准不只统一了写法,还留了「换一种更省的写法」的余地。**
**HTTP/1.1 到 HTTP/2 换了编码方式,但语义一个字没改——请求还是请求,头还是头。**

65 字节对 4 字节,差了十六倍。 这不是压缩算法的功劳,是「预先约好的表」的功劳—— 双方都存着同一张 61 项的静态表,编号就代表内容。 注意这四项的索引是 1、2、4、7,不是 1、2、3、4:静态表的顺序是 RFC 定死的, 中间几项被别的头占着。所以「索引号」本身也是标准的一部分,不能自己排。 第一行那个纯文本长度 65 是在没有 Host 域名的前提下算的: 真实请求里光一个 User-Agent 就可能上百字节,所以头部压缩在移动网络上收益更明显。

30 秒实验

  1. 把 :authority 的域名换成 www.baidu.com,看 1.1 的字节数变多少、 2 的变多少——想想为什么 2 不变。
  2. 查 RFC 7541 附录 A 的静态表,数出一共多少项。
  3. 想一个「语义不变、写法可以大改」的其他例子(提示:编码、序列化格式都算)。

常见误区

  1. 以为标准化就是「只有一种正确实现」。 标准约束的是对外行为,内部结构、语言、 性能优化全都不管。
  2. 以为 MUST 和 SHOULD 只是措辞差别。 它们是合规性级别:违反 MUST 就是不合规, 别人有权不兼容你。
  3. 以为协议升级必然要改应用代码。 HTTP/2 换了编码没换语义, 业务代码一行不用动——这也正是它能推得动的原因。
自测三问(点开对答案) 1. 标准管什么、不管什么? —— **管对外行为:报文格式、状态码语义、时序。** **不管内部:数据结构、编程语言、性能优化。**正因为不管内部,各家实现才能共存。 2. MUST、SHOULD、MAY 的区别是什么? —— **MUST 是硬要求,违反即不合规;SHOULD 是「有理由可以不做」;MAY 是完全可选。** 读协议时先找这三个词,效率最高。 3. HTTP/2 把四个头部从 65 字节压到 4 字节,靠的是什么? —— **靠双方预先约好的静态表**:常见头在表里有固定索引,直接发编号。 **语义没变,只换了编码**,所以应用代码不用改。

下一节进入 1.5:分层——讲清楚为什么要分层、分层怎么让「换一层不影响其他层」成立。

相关笔记