📘 第九章 基于HTTP的功能追加协议
📄 一句话概括

HTTP 只解决了「取一个资源」,别的需求它做不了:要实时、要并行、要改服务器上的文件, 它都没有对应的表达方式。所以人们在它周围打补丁——有的换协议,有的换传输,有的只加方法。

📝 本节和前面几节的关系

前八章全在 HTTP 内部拆零件。 这一章抬头往外看:HTTP 之外长了哪些东西,它们各自补的是哪一块短板。

一、HTTP 的目标其实很窄

从第一章到第八章,HTTP 一直在做同一件事:客户端说「我要哪个资源」,服务端把它送回来。 方法就那么几个(2.5),全是围绕「操作资源」设计的。 这套设计活了二十多年,因为当年的页面就是文档。 可页面变成应用之后,需求换了三样:要实时(聊天、行情、协同编辑), 要快(一个页面几十上百个资源),要能写(在线改服务器上的文件)。 这三样 HTTP 一样都答不好。不是实现得糙,是协议里根本没有这个表达方式——就像用信封送不了直播。

二、补丁打在三个不同的地方

第一类:应用层换一个协议,但借 HTTP 的门进来。 代表是 WebSocket,握手那一下还是 HTTP 请求,谈成之后这条连接就不再跑 HTTP 了(9.3)。 第二类:语义不动,改传输方式。 代表是 SPDY,它把请求切成帧、在一条连接上交错发送、压首部、还能主动推送; 这套东西后来变成了 HTTP/2.0(9.2、9.4)。 第三类:协议主体不动,只加方法和状态码。 代表是 WebDAV,专门管「服务器上的文件」,加了 MKCOL、COPY、MOVE、LOCK 这些(9.5)。 三类的野心依次递减:换协议最彻底,也最难推;加几个方法最轻,但只能解决一个垂直场景。

三、为什么都要借 HTTP 的门

一个新协议最大的成本不是写代码,是让它在真实网络里跑得通。 80 和 443 这两个端口几乎所有防火墙都放行,别的端口经常被拦。 还有一整套现成的东西:代理认得 HTTP(5.2.1),缓存认得,认证认得,浏览器更是只肯为一个协议做特例。 绕开它,等于把这些全部重写一遍。 所以新协议几乎都以一个 HTTP 请求开局,谈成了再换——这叫协议升级, Upgrade 首部就是干这个的(6.3.7)。WebSocket 和 HTTP/2 的 h2c 都走这条路。

四、动手 Demo:三个数字,看出 HTTP 的三处短板

一个只会「问一句答一句」的本机服务端,每次处理故意花 20 毫秒。 先用一条连接串行发 8 个请求,再用 8 条连接并行发同样的 8 个,对比总耗时。 然后连上去一句话不说,看服务端会不会主动发点什么。最后量一下一条真实浏览器请求的首部有多少字节。

# 串行:一条连接,一个一个来
s = socket.create_connection((u'127.0.0.1', port))
for i in range(8):
    s.sendall(req(u'/item%d' % i))
    recv_response(s)

# 并行:八条连接,各发各的
for i in range(8):
    Thread(target=one, args=(i,)).start()

实跑输出:

--------------------------------------------------------------------
一条连接串行,还是八条连接并行
--------------------------------------------------------------------
  服务端每次处理耗时         20 毫秒(Demo 里故意加的)
  串行 8 个请求              193.0 毫秒
  并行 8 个请求              42.3 毫秒
  串行/并行                 4.6 倍

  沉默 350 毫秒收到          0 字节
  沉默之后发一个请求         HTTP/1.1 200 OK

  最后一行是本节的重点:连着但不说话,服务端一个字都不会主动发过来。
  HTTP 是「问一句答一句」,所以服务器有新数据时只能等下次被问。
  一条浏览器式请求首部       232 字节
  正文(一个「ok」)         2 字节
  首部是正文的               116 倍

串行 193.0 毫秒,并行 42.3 毫秒,差 4.6 倍。 差别不在带宽,在「等」——串行时八次等待叠在一起,并行时它们重叠了。 沉默 350 毫秒,收到 0 字节。服务端明明连着,就是不发。 这是 HTTP 的根本约束:它没有「我给你推一条」这种语法。 沉默之后发一个请求,立刻回 HTTP/1.1 200 OK。所以它不是不会说话,是只肯被问。 这一条决定了后面所有实时方案的样子。 一条浏览器式请求的首部 232 字节,正文只有 2 字节,首部是正文的 116 倍。 一个页面几十个资源,这份路费要重复付几十遍。

30 秒实验

  1. 把服务端那 20 毫秒改成 200 毫秒,看串行与并行的倍数会不会变。
  2. 把并行那组的线程数改成 2,看总耗时怎么变——为什么不是简单的四分之一?
  3. 数一数你自己打开一个常用网站时,浏览器发了多少个请求。

常见误区

  1. 「HTTP 慢是因为带宽不够」——多数时候是等,不是挤。
  2. 「服务端可以随时推数据给客户端」——不能,HTTP 只有应答,没有主动发送。
  3. 「新协议都是推倒重来」——绝大多数都借 HTTP 的门进来,再谈升级。
自测三问(点开对答案) 1. HTTP 最根本的交互方式是什么? —— 客户端问、服务端答。没有「服务端主动发」这种语法。 2. 这一章讲的三个方向分别改的是什么? —— WebSocket 换应用层协议,SPDY/HTTP/2 改传输方式,WebDAV 只加方法和状态码。 3. 为什么新协议常常要先发一个 HTTP 请求? —— 为了借用端口、代理、缓存、认证这些现成的基础设施,谈成再升级。

先别急着看新协议。得先把「HTTP 到底卡在哪」量清楚,否则不知道补丁要打在哪儿。

相关笔记