📘 第二章 简单的HTTP协议
📄 一句话概括

HTTP 的两个角色不是「两台机器」,是两个角色:先发请求的那个是客户端, 回请求的那个是服务端。同一台机器可以两者都是。

📝 本节和前面几节的关系

第一章走完了整条链路。从这一节起拆报文本身,第一个问题是:这条连接的两端分别是谁、谁先说话。

一、角色由「谁先发起」决定

HTTP 是请求/响应模型:永远是客户端先发请求,服务端再回响应。 服务端不能主动推消息(要推得用 WebSocket,见 9.3 节)。 这条规矩决定了三件事:谁先建立 TCP 连接、谁先发第一个字节、 以及服务端能不能在没有请求的时候发东西——答案是不能。 Demo 里本机自连自:一个进程连自己起的服务端,127.0.0.1 既是客户端地址也是服务端地址, 只有端口不同。所以「客户端」「服务端」是角色名,不是身份名。

二、一请求一响应,一对一

一个请求只对应一个响应。 这一点在 HTTP/1.1 里尤其严格:同一个连接上,请求和响应不能交错——发了请求就得等响应回来, 除非用管线化(2.7.2),而它已经废弃了。 HTTP/2 打破了这个限制:同一条连接上可以有多个并行的流,每个流有自己的编号,响应可以乱序。 但「谁先发起」这条没变——仍然是客户端先开口。

三、服务端看见的只有请求

服务端拿不到「用户是谁」「上次问了什么」「浏览器开了几个标签」,它看到的只有这一段字节:请求行、 首部、正文。全部信息都在这段字节里。 这条限制看起来苛刻,其实是 HTTP 能活这么久的原因:服务端不需要记住任何东西, 所以任何一个实例都能处理任何一个请求,加机器就能扩容。代价是每个请求都要把上下文重说一遍。

四、动手 Demo:自己连自己:一条连接上的两个角色

在本机起一个服务端,再用裸 socket 连上去,把客户端这边用的端口和服务端记下的对端地址都打出来。

def resp(raw, st):
    first, hd, body = H.parse_headers(raw)
    seen[u'line'] = first
    return (u'HTTP/1.1 200 OK', [(u'Content-Type', u'text/plain')],
            u'我是服务端'.encode())
with H.server_multi(resp, keepalive=False) as port:
    s = socket.create_connection((u'127.0.0.1', port), timeout=2.0)
    local = s.getsockname()          # 客户端这边用的端口
    H.MULTI.state[u'peer'] = local
    s.sendall(_req())                # GET /index.html HTTP/1.1
    got = H.read_all(s)

实跑输出:

--------------------------------------------------------------------
谁是客户端、谁是服务端
--------------------------------------------------------------------
  本机既是客户端也是服务端   127.0.0.1
  客户端这边用的端口         56487(内核临时分配)
  服务端看见的请求行         GET /index.html HTTP/1.1
  服务端记下的对端           127.0.0.1:56487
  客户端收到的               HTTP/1.1 200 OK,正文「我是服务端」

  角色不是由机器决定的,是由**谁先发起**决定的。
  同一台机器、甚至可以同一个进程,发请求时是客户端,回请求时是服务端。
  连接一旦建立,**两个方向都能传数据**——HTTP 只规定谁先说话。

客户端这边用的端口 56428(内核临时分配)、服务端记下的对端 127.0.0.1:56428——两个地址完全一样, 只有端口不同。一条 TCP 连接由「源 IP + 源端口 + 目的 IP + 目的端口」四元组唯一确定。 服务端看到的请求行是 GET /index.html HTTP/1.1,它从这行里只知道「要什么」,不知道「谁要」。

30 秒实验

  1. 把 keepalive=False 改成 True,在同一条连接上连发三次,看服务端记下的对端端口会不会变。
  2. 同时开两个 socket 连上去,比较两个客户端端口——应该是两个不同的数。
  3. 想一想:如果服务端在没收到请求时主动发数据,客户端会怎么处理?

常见误区

  1. 「服务端是那台性能好的机器」——角色按行为分,不按机器分。
  2. 「客户端只能发、服务端只能收」——连接建立后两个方向都能传数据,HTTP 只规定谁先说话。
  3. 「服务端能主动推送」——原生 HTTP 不行,要推送得靠长轮询、SSE 或 WebSocket。
自测三问(点开对答案) 1. HTTP 里谁是客户端? —— 先发起请求的那一方。同一个程序在不同时刻可以是客户端也可以是服务端。 2. 一条 TCP 连接靠什么唯一确定? —— 源 IP、源端口、目的 IP、目的端口,四元组。 3. 为什么 HTTP/1.1 里一个连接一次只能有一个在途请求? —— 协议用「请求—响应」严格配对,没有编号机制区分响应属于哪个请求。

角色分了。下一节看它们交换的那两段字节长什么样。

相关笔记