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

协议不只规定「对的怎么写」,还规定「错了怎么答」。 没有明确的拒绝,对端只能干等或乱猜——这才是协议真正要解决的问题。

📝 本节和前面几节的关系

1.3.1 讲了协议是什么,这一节讲「没有协议会怎样」。 1.3.3 会用人对话打比方,把这套规则讲得更具体。

一、没有协议的三个后果

假设两台机器都没有协议,只有一根线连着:

后果 具体表现 协议怎么解决
不知道从哪开始 收到一串字节,分不清头尾 起始行 + 固定格式
不知道谁该说话 两边同时发,撞车 请求/应答模型
不知道错在哪 收到乱码,无法定位 错误码 + 校验

第二行那个「撞车」是最早的真实问题:共享介质上的设备会同时发,信号叠加成噪声。 所以以太网设计了一整套「先听后发、冲突重发」的规则(CSMA/CD)。 第三行那个「不知道错在哪」是排障成本的最大来源: 有了错误码,问题能定位到「哪一层、哪一类」;没有错误码,只能靠猜。

二、错误码的设计

HTTP 的状态码分五类,第一位就表明了性质:

范围 含义 典型
1xx 还在处理 100 Continue
2xx 成功 200 OK
3xx 要你换个地方 301 永久重定向
4xx 你这边有问题 400、401、404
5xx 我这边有问题 500、502、503

第四行与第五行的分界最重要:4xx 是「客户端错了」,5xx 是「服务端错了」。 这个分界决定了「谁来修」。 所以排障时看到 4xx 先查自己发的请求,看到 5xx 先查服务端日志——方向一开始就不能错。 这也说明协议设计里的「分类」不是整理癖,它是给排障者的一条最短路径。

三、协议也是一种防御

明确的拒绝能挡住的东西,比想象的多:

情形 有协议时 没协议时
格式错误 立即回 400 可能一直等
超长请求 回 414 或断开 缓冲区溢出
未知方法 回 501 行为不确定

第二行那个「缓冲区溢出」是历史上一大类漏洞的来源:不检查长度,直接往固定大小的缓冲区里拷。 所以「按协议拒绝」不只是礼貌,是安全边界——协议里那些「必须拒绝」的条款,本质是防御规则。 这也是为什么写服务端第一件事是「参数校验」:校验就是执行协议里的拒绝条款。

四、动手 Demo:不按协议来,对端会怎么处理

起一个最小的 HTTP 服务,发四条请求:一条标准、三条故意写错。 看错误的那三条各自拿到什么回应——关键是「有没有明确拒绝」。

import socket, threading, http.server, socketserver

class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Length', '2')
        self.end_headers()
        self.wfile.write(b'ok')
    def log_message(self, *a):
        pass

httpd = socketserver.TCPServer(('127.0.0.1', 0), H)
port = httpd.server_address[1]
threading.Thread(target=httpd.serve_forever, daemon=True).start()

cases = [('标准', b'GET / HTTP/1.0\r\n\r\n'),
         ('缺方法', b'/ HTTP/1.0\r\n\r\n'),
         ('版本错', b'GET / HTTP/9.9\r\n\r\n'),
         ('方法乱写', b'FOO / HTTP/1.0\r\n\r\n')]
for tag, raw in cases:
    c = socket.create_connection(('127.0.0.1', port), timeout=2)
    c.sendall(raw)
    buf = b''
    while True:
        d = c.recv(512)
        if not d:
            break
        buf += d
    c.close()
    first = [x for x in buf.decode('latin-1').split('\r\n')
             if x.startswith('HTTP/')]
    print('%-10s %s' % (tag, first[0] if first else '(无状态行)'))
httpd.shutdown()

实跑输出:

--------------------------------------------------------------------
不按协议来,对端会怎么处理
--------------------------------------------------------------------
**协议的「必要性」最好用反例证明**:故意发一条格式不对的请求,看对端是崩掉、还是明确拒绝。

服务端已起在 54170,只认标准 HTTP 请求。

  情形         请求字节     首行应答
  -----------------------------------------------------------
  标准请求     18           HTTP/1.0 200 OK
  缺方法       14           <!DOCTYPE HTML>
<html lang="en">
  版本写错     18           <!DOCTYPE HTML>
<html lang="en">
  方法乱写     18           HTTP/1.0 501 Unsupported method

**四条里只有第一条拿到 200,其余全被明确拒绝。**

**这就是协议存在的意义**:**它不只规定「对的怎么写」,还规定「错了怎么答」。**
**没有 400 这一类明确拒绝,对端只能干等或乱猜。**

四条里只有第一条拿到 200,其余三条都被明确拒绝。 答案行不是从响应体的第一行取的——Python 的错误页会先输出 HTML,状态行夹在中间, 所以要遍历找 HTTP/ 开头的那一行。 这个细节本身就是个教训:解析响应要按协议找状态行,不能假设「第一行就是」。 很多简易客户端就栽在这里。

30 秒实验

  1. 再加一条超长 URL 的请求(比如路径里放 10000 个 a),看返回 414 还是直接断开。
  2. 发一条不带 \r\n\r\n 的请求再关掉连接, 看服务端会不会一直等——这就是「没有明确结束标记」的后果。
  3. 用 curl -v 发同样的四条请求,看它的输出和你手工发有什么不同。

常见误区

  1. 以为协议只是「格式说明」。 它还包括「异常处理」, 而且异常部分往往更重要——它决定了系统出错时的行为。
  2. 以为拒绝请求是「不友好」。 明确拒绝是安全边界,含糊处理才是隐患。
  3. 以为 4xx 和 5xx 只是数字不同。 它们划的是责任边界:4xx 你改,5xx 我改。 方向错了会浪费大量时间。
自测三问(点开对答案) 1. 没有协议会带来哪三个后果? —— **不知道从哪开始(分不清头尾)、不知道谁该说话(撞车)、不知道错在哪(无法定位)。** 协议分别用起始行、请求应答模型、错误码解决。 2. HTTP 状态码 4xx 与 5xx 的分界意味着什么? —— **责任归属**:4xx 是客户端错了,5xx 是服务端错了。**排障时先看这个分界,方向就不会错。** 3. 为什么说「按协议拒绝」是一种防御? —— **因为不检查长度、不校验格式,就会出缓冲区溢出这类漏洞。**协议里「必须拒绝」的条款, 本质是防御规则。

下一节换个更容易懂的说法:把协议比成人跟人的对话。

相关笔记