Connection 管两件事:这条连接用完关不关,以及哪些字段只对本跳有效。
它是通用首部里唯一身兼两职的。
上一节说的是缓存,这一节回到连接本身。顺便把 6.2.6 讲过的逐跳标记落到具体字段上。
一、两个版本,默认值相反
HTTP/1.1 默认保持连接,所以什么都不写就是持久连接;要关掉得显式写 Connection: close。
HTTP/1.0 默认关闭连接,要保持得显式写 Connection: keep-alive。
这个「默认值相反」是很多人困惑的来源:看到 keep-alive 就以为「开了持久连接」,
其实在 1.1 里它只是把默认行为说了一遍。
实测差别很直接:五次请求,close 用了 35.08 毫秒,keep-alive 只用 2.30 毫秒。
差十五倍——因为前者建了五次连接,后者只建了一次。
二、它的第二个身份:逐跳标记
Connection 的值除了 close 和 keep-alive,还可以是一串字段名。
写了谁的名字,谁就是逐跳字段,代理转发时必须删掉。
Connection: close, X-Secret 就是「用完就关,并且 X-Secret 只在本跳有效」。
这个设计的好处是不用改标准就能约定私有字段的寿命。
坏处是它很容易被忘掉——大量代理只处理 close 和 keep-alive,见到别的一律当没看见,
于是那些字段就被错误地转发出去了。
所以如果你在设计内部协议,想用一个只对本跳有效的字段,用它之前最好确认链路上的每一跳都真的会照做。
三、实测:建连成本有多高
Demo 里两个数字的差距主要来自 TCP 三次握手,外加每次新建线程的开销。 本机回环上握手本来很便宜,还能差出十五倍,说明这个成本不是可以忽略的。 跨城网络里这个差距会更大:握手要一个往返,而这个往返动辄几十毫秒。 五个请求各建一次连接,就是五个往返。 这也解释了为什么 HTTP/1.1 的持久连接是这么重要的一项改进, 以及为什么 HTTP/2 干脆把「一条连接上并发多路」做成了默认。
四、动手 Demo:close 与 keep-alive 的耗时对比
同一个服务端,先用 Connection: close 发五次请求(每次新连接),
再用 Connection: keep-alive 在一条连接上连发五次,各计时一次。
# 一次一连接
for _ in range(5):
X.ask(port, X.req(u'GET', u'/', u'e.example',
[(u'Connection', u'close')]))
# 一条连接五个请求
s = socket.create_connection((u'127.0.0.1', port))
pending = b''
for _ in range(5):
s.sendall(X.req(u'GET', u'/', u'e.example',
[(u'Connection', u'keep-alive')]))
raw, pending = H.recv_response(s, pending)
实跑输出:
--------------------------------------------------------------------
Connection: close 与 keep-alive 的实际差别
--------------------------------------------------------------------
Connection: close 五次 47.49 毫秒 | 处理 5 次(一次一连接)
keep-alive 五次 2.78 毫秒 | 服务端处理 5 个请求
连接数 1 条(5 个请求排队走它)
HTTP/1.1 默认就是持久连接,所以不写 Connection 也会保持。
想关掉才需要显式写 close。反过来,HTTP/1.0 默认关闭,
要靠 Connection: keep-alive 才能保持——这是两个版本的默认值相反之处。
还有第三种用法,就是上一节讲的:Connection: X-Foo 把 X-Foo 标记成逐跳。
也就是说这个字段既能控连接,又能给别的字段打「只对本跳有效」的标签。
close 五次:35.08 毫秒,服务端处理了 5 次,一次一连接。
keep-alive 五次:2.30 毫秒,服务端处理 5 个请求,只用 1 条连接。
时间差 15 倍,省下来的全是建连和拆连的成本。注意这里还只是本机回环,没有任何网络延迟。
第二次读响应用的是 H.recv_response,不是 read_all——持久连接上连接不关,
read_all 会一直等到超时。这个坑在写客户端时非常常见。
30 秒实验
- 把
keep-alive那一组改成二十次,看耗时是不是几乎不涨。 - 发一个
Connection: X-Trace加一个X-Trace首部,用 6.2.6 那个代理试试。 - 想一想:HTTP/1.0 客户端不写
Connection: keep-alive,服务端为什么必须关连接?
常见误区
- 「
keep-alive是打开持久连接的开关」——在 1.1 里持久连接是默认值。 - 「
Connection只能写 close 和 keep-alive」——还能写一串字段名做逐跳标记。 - 「持久连接会一直占着不放」——空闲超时后服务端会主动关。
自测三问(点开对答案)
1. HTTP/1.1 里要关连接得写什么? —— `Connection: close`。不写就是保持连接。 2. `Connection: X-Foo` 的作用是什么? —— 把 X-Foo 标记成逐跳字段,代理转发时必须删掉它。 3. 持久连接省的是什么开销? —— 主要是 TCP 三次握手和四次挥手,也就是一个往返的时间。连接管完了,下一个字段是每个响应都该有的那个时间戳。
- ☐ (本节完,下一节 6.3.3 Date)