2006年,Dojo框架的创始人Alex Russell在他的博客上发表了一篇题为《Comet: Low Latency Data for the Browser》的文章。文中描述了一种令人沮丧的处境:Web应用需要实时更新,但HTTP协议的设计初衷是为超媒体文档服务,而非双向通信。开发者被迫用各种"hack"手段——隐藏iframe、长轮询、甚至Flash——来模拟服务器推送。Russell将这些技术统称为"Comet",并预言这将是Web开发者的长期痛点。

三年后,2009年12月,Google Chrome 4成为首个原生支持WebSocket的浏览器。2011年,IETF发布RFC 6455,WebSocket正式成为国际标准。又过了十五年,当我们在Slack上聊天、在交易平台盯盘、在协作文档中同步编辑时,很难想象这些实时功能曾经需要如此复杂的变通方案。

WebSocket取代HTTP长轮询并非偶然。这不是一场简单的技术迭代,而是Web基础设施从"请求-响应"模型向"事件驱动"模型演进的关键转折。理解这场变革,意味着理解协议设计的本质权衡:效率与兼容性、简洁与安全、标准化与灵活性。

HTTP的先天局限:为文档设计的协议

HTTP协议诞生于1989年,Tim Berners-Lee设计它的初衷很简单:让物理学家能够方便地共享超文本文档。请求-响应模型完美契合这个场景——客户端需要什么,就请求什么;服务器有什么,就返回什么。

问题出现在Web应用开始需要"反向"通信时。想象一个在线聊天室:用户A发送消息,服务器需要立即通知用户B、C、D。但HTTP的设计假设服务器是被动的,只有客户端主动发起请求,服务器才能响应。

开发者能做什么?最直观的方案是轮询(Polling):客户端每隔几秒向服务器询问"有新消息吗?"。简单,但代价高昂。假设轮询间隔是5秒,每秒1000个在线用户意味着每秒200次HTTP请求。每条HTTP请求都带着完整的头部——Host、User-Agent、Cookie、Accept……一个典型的HTTP头部可能占用数百甚至上千字节。如果实际消息只有"你好"两个字(6字节),传输效率可能低至1%以下。

更严重的是延迟。5秒的轮询间隔意味着平均2.5秒的消息延迟。这在即时通讯场景中几乎不可接受。缩短间隔?服务器负载线性增长,网络带宽持续浪费。

长轮询(Long Polling)是对轮询的改进:客户端发起请求后,服务器不立即响应,而是将连接挂起,直到有新消息或超时。这确实降低了不必要的请求次数,但本质问题依然存在——每条消息都需要一次完整的HTTP请求-响应循环。

Alex Russell在2006年的文章中这样描述Comet架构:

Comet applications can deliver data to the client at any time, not only in response to user input. The data is delivered over a single, previously-opened connection.

理论上,长轮询已经接近这个目标。实践中,HTTP的语义模型带来了新的麻烦。

Comet的技术困境:HTTP语义的妥协

Comet不是一个具体技术,而是多种服务器推送技术的统称。除了长轮询,还包括HTTP StreamingForever Frame

HTTP Streaming的思路是:既然要复用连接,为什么不让它一直开着?服务器在响应中设置Transfer-Encoding: chunked,然后持续写入数据块。客户端读取到数据块就处理,连接不断开。这听起来完美,但HTTP的语义模型再次成为障碍。

HTTP/1.1的持久连接(Connection: keep-alive)设计初衷是让多个请求复用同一个TCP连接,而非让一个请求无限期延长。代理服务器、负载均衡器、防火墙——中间件链路中的任何一环都可能对"超长"的请求做出自己的判断,主动断开连接。结果是,HTTP Streaming在各种网络环境下的表现极不稳定。

Forever Frame是另一种变通:在页面中嵌入一个隐藏的<iframe>,其src指向一个永不结束的HTTP请求。服务器通过向这个iframe写入<script>标签来推送数据,浏览器解析后执行JavaScript。这个技巧确实能绕过某些代理的限制,但引入了新的复杂性:浏览器对iframe的跨域限制、内存泄漏、难以处理的错误恢复。

更根本的问题在于消息顺序。HTTP/1.1允许浏览器对同一域名发起多个并发请求(通常是6个)。当多个长轮询请求同时在进行时,无法保证它们的响应按预期顺序到达。网络抖动、代理延迟、服务器处理速度差异——任何一个因素都可能打乱消息顺序。

2008年,WebSocket的两位设计者Michael Carter和Ian Hickson在IRC和W3C邮件列表中讨论这些问题时,得出的结论是:HTTP协议的语义模型与实时通信的需求存在根本冲突。需要的不是更好的hack,而是一个全新的协议。

但这个新协议面临一个现实约束:它必须能在现有Web基础设施上运行。防火墙通常只开放80和443端口;企业代理可能拦截未知协议;浏览器的安全模型基于同源策略。WebSocket的设计必须在这些约束下工作。

RFC 6455的设计哲学:HTTP外衣下的TCP

RFC 6455开篇这样描述WebSocket的设计目标:

The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code.

关键词是"controlled environment"和"opted-in"。浏览器是受控环境,运行的是不可信的JavaScript代码;服务器通过特定的握手过程"选择加入"WebSocket通信。这个设计哲学贯穿整个协议。

HTTP Upgrade握手

WebSocket连接始于一个HTTP请求:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Version: 13

这是一个标准的HTTP/1.1 GET请求,但带有几个特殊头部。Upgrade: websocketConnection: Upgrade的组合遵循HTTP/1.1的协议升级机制,理论上应该被任何HTTP/1.1兼容的中间件正确处理。

Sec-WebSocket-Key的值是一个Base64编码的16字节随机数。服务器必须证明它正确解析了这个握手请求——这是防止跨协议攻击的关键。服务器的响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Accept的计算方式非常精妙:将客户端的Sec-WebSocket-Key与固定字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接,然后进行SHA-1哈希,再Base64编码。这个"魔法字符串"的存在,使得WebSocket握手不太可能被其他协议意外触发。

101状态码表示"协议切换"。从这一刻起,TCP连接不再是HTTP连接,而是WebSocket连接。后续的数据帧遵循完全不同的格式。

最小化的帧格式

RFC 6455的设计哲学是"最小化帧封装":

The WebSocket Protocol is designed on the principle that there should be minimal framing.

帧格式的定义如下:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+

最小的有效帧只有2字节:一个服务器发送的无负载关闭帧。对于125字节以下的短消息,头部开销只有2字节。即使是最长的头部(14字节),也远小于典型的HTTP请求头。

可变长度的负载长度字段是一个精巧的设计:7位直接存储(0-125字节),16位扩展存储(126表示后续2字节为长度),64位扩展存储(127表示后续8字节为长度)。这意味着WebSocket理论上支持高达2^64字节的消息——足够传输任何合理的数据量。

掩码机制:代理缓存投毒的防御

帧格式中有一个看似奇怪的字段:Masking-key。客户端发送的每一帧都必须包含掩码,并对负载数据进行XOR操作;服务器发送的帧则不允许掩码。

这是针对特定攻击向量的防御。RFC 6455第10.3节解释了背景:

某些HTTP代理会缓存响应。如果恶意网页能够控制发送给代理的字节序列,它可能构造出看似合法HTTP响应的数据包。当其他用户请求相同URL时,代理返回缓存的恶意内容——这就是代理缓存投毒攻击。

掩码机制使得客户端发送的数据在代理看来是"随机"的,无法被解析为有效的HTTP响应。服务器必须在处理前解除掩码,但这个过程对应用层透明。

掩码的设计也反映了一个有趣的权衡:它增加了协议复杂性(每个客户端帧都需要额外的4字节和XOR操作),但消除了一整类安全漏洞。在浏览器环境中,这个权衡是合理的。

性能对比:量化的差异

2018年,FeathersJS的作者David Luecke进行了一项基准测试,对比HTTP轮询和WebSocket的性能。在相同硬件条件下,HTTP轮询的吞吐量约为950请求/秒,而WebSocket(通过Socket.IO)达到了约3900请求/秒——超过4倍的提升。

但这只是吞吐量。延迟的差异更为显著。

连接建立的代价

一次完整的HTTP请求需要:

  1. TCP三次握手(1-RTT)
  2. TLS握手(如果使用HTTPS,通常2-RTT)
  3. HTTP请求/响应(1-RTT)

假设往返时间(RTT)为50ms,一个HTTPS请求的理论最小延迟是200ms。这还不包括服务器处理时间和网络排队延迟。

WebSocket的初始连接同样需要这个过程,但只需一次。握手成功后,每条消息的延迟仅为:

  • 客户端到服务器:1个RTT(发送数据帧)
  • 服务器到客户端:1个RTT(发送数据帧)

对于双向通信场景,WebSocket的延迟优势是数量级的。

头部开销对比

一个典型的HTTP请求头部:

POST /api/messages HTTP/1.1
Host: chat.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Cookie: session_id=abc123; user_prefs=xyz789
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...
Accept: application/json
Content-Length: 42

{"message": "Hello, world!"}

头部大小通常在500-2000字节之间,而消息体可能只有几十字节。

WebSocket的等价帧:

  • 头部:2-14字节
  • 消息体:16字节(“Hello, world!“的UTF-8编码)

头部开销降低了一个数量级。

并发连接的可扩展性

Ably(一家实时消息平台提供商)在其技术博客中分享的基准测试显示,单个优化过的WebSocket服务器节点可以维持高达24万个并发连接,同时保持P99延迟低于50毫秒。

对比长轮询:每个"连接"实际上是重复创建的HTTP请求。每次请求都需要服务器分配资源处理、数据库查询(如果有认证)、业务逻辑执行。即使请求很快返回,频繁的创建/销毁循环也消耗大量CPU和内存。

安全模型:Web的同源策略延伸

WebSocket的安全模型基于HTTP的Origin机制,但有所不同。

HTTP的CORS(跨源资源共享)通过响应头Access-Control-Allow-Origin控制哪些源可以读取响应。WebSocket握手不包含CORS检查,因为它不是HTTP请求——握手成功后,浏览器不会将响应暴露给JavaScript,而是建立二进制通道。

取而代之的是Origin验证。服务器在握手阶段检查Origin头,决定是否接受连接。这个验证必须由服务器实现,协议层面没有强制要求。

这带来了一个常见的漏洞:跨站WebSocket劫持(Cross-Site WebSocket Hijacking, CSWSH)。如果服务器错误地信任Origin或完全不验证,恶意网页可以发起连接并读写数据——就像跨站请求伪造(CSRF),但具有双向通信能力。

OWASP的WebSocket安全指南建议:

  1. 总是验证Origin头
  2. 在握手阶段进行认证(如通过查询参数或Cookie)
  3. 使用wss://(WebSocket over TLS)加密传输
  4. 实施输入验证和速率限制

局限与演进:WebSocket之后

WebSocket并非完美。2015年5月,IETF发布RFC 7540,HTTP/2正式标准化,引入了服务器推送和多路复用。一个问题浮出水面:WebSocket是否仍然是必需的?

HTTP/2的关系

HTTP/2的服务器推送与WebSocket的服务器推送本质不同。HTTP/2推送的是缓存资源(CSS、JS、图片),而非实时数据。理论上,HTTP/2的流(Stream)机制可以用于双向通信,但浏览器API不支持这种用法。

更根本的是,HTTP/2仍然是请求-响应模型。服务器无法主动向客户端发送非资源数据。WebSocket的全双工能力在这个意义上是不可替代的。

WebTransport:下一代的挑战

2022年,WebTransport API开始进入主流浏览器。Chrome 97在2022年1月率先支持,Safari 15.4紧随其后,Firefox则在2023年6月的版本114中完成支持。WebTransport基于HTTP/3(QUIC协议),提供更底层的传输能力:

  • 支持不可靠数据报(适合游戏、实时音视频)
  • 支持可靠流(类似WebSocket)
  • 内置多路复用,无队头阻塞

WebTransport的设计弥补了WebSocket的几个局限:

  1. 队头阻塞:WebSocket基于TCP,单个丢包会阻塞整个连接。WebTransport基于QUIC,独立流互不影响。

  2. 二进制协议扩展:WebSocket的子协议机制在实践中使用较少。WebTransport直接支持Datagram API,适合低延迟场景。

  3. 与HTTP/3的集成:WebTransport可以复用HTTP/3连接,减少连接数。

但WebTransport也有代价:服务器实现复杂度更高,企业防火墙对QUIC的支持参差不齐。

截至2025年,WebSocket仍然是实时Web通信的主流选择。根据Can I Use的数据,WebSocket在全球浏览器中的支持率超过99%。WebTransport的支持率约为85%,且主要集中在最新版本的现代浏览器。

技术权衡的本质

回顾WebSocket取代HTTP长轮询的历程,核心是三个权衡:

协议复杂性 vs 通信效率

WebSocket的帧格式、掩码机制、分片支持,都增加了协议的复杂性。开发者通常不会直接处理这些细节,而是使用库(如Socket.IO、ws、Gorilla WebSocket)。但复杂性转移到了库的实现者身上。相比之下,HTTP长轮询的语义简单得多——就是普通的HTTP请求,任何HTTP客户端都能处理。

收益是显著的:数量级的延迟降低、带宽节省、服务器负载降低。对于实时通信场景,这个权衡是明确的。

标准化 vs 灵活性

WebSocket协议定义了帧格式、握手过程、控制帧,但没有定义消息格式、认证方式、重连逻辑、心跳机制。这些都需要应用层实现。Socket.IO等库提供了这些功能,但引入了库特定的协议扩展。

权衡的结果是:使用原生WebSocket需要处理更多细节;使用库则增加了依赖和潜在的兼容性问题。HTTP长轮询在这方面更"原生”——任何HTTP基础设施都能支持。

兼容性 vs 性能

WebSocket的握手设计为HTTP Upgrade请求,是为了最大程度兼容现有基础设施。但这也带来了限制:无法使用非HTTP端口,无法在握手前协商压缩(压缩扩展通过Sec-WebSocket-Extensions头部协商,但支持不完整),无法绕过某些企业代理的WebSocket拦截。

纯粹的TCP socket会更高效,但无法在浏览器环境中使用。WebSocket是在Web约束下的最优解。

结语

2011年RFC 6455发布时,WebSocket的两位编辑在文档中写道:

The WebSocket Protocol is designed to supersede existing bidirectional communication technologies that use HTTP as a transport layer.

“取代"是一个强烈的词。十五年过去,WebSocket确实成为实时Web通信的事实标准,但"取代"的过程并非一蹴而就。

企业防火墙的兼容性问题持续了多年;浏览器API的差异需要库来抽象;移动网络下的连接稳定性需要额外的心跳机制。直到今天,某些场景下长轮询仍然是一个务实的选择——当实时性要求不严格、连接数很少、或者需要在极老的浏览器上运行时。

技术的演进不是线性的替代,而是生态位的分化。WebSocket占据了实时通信的生态位,HTTP长轮询退居边缘但并未消失。WebTransport正在开辟新的生态位——低延迟、容忍丢包的场景。

理解这些权衡,比记住任何具体协议的细节更有价值。协议是解决特定问题的工具,每个设计决策背后都是一系列取舍。当我们选择一个协议时,实际上是在选择接受它所有的权衡。


参考资料

  1. Fette, I., & Melnikov, A. (2011). RFC 6455: The WebSocket Protocol. IETF.
  2. Russell, A. (2006). Comet: Low Latency Data for the Browser. infrequently.org.
  3. RFC 7692: Compression Extensions for WebSocket. (2015). IETF.
  4. RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2). (2015). IETF.
  5. MDN Web Docs: Writing WebSocket servers. Mozilla Developer Network.
  6. Ably Blog: WebSockets vs Long Polling. ably.com.
  7. WebSocket Security Cheat Sheet. OWASP.
  8. Can I Use: WebSockets. caniuse.com.
  9. Luecke, D. (2018). HTTP vs Websockets: A performance comparison. blog.feathersjs.com.
  10. The Road to WebSockets. websocket.org.
  11. RFC 8441: Bootstrapping WebSockets with HTTP/2. (2018). IETF.
  12. Chromium Blog: Chrome 97: WebTransport, New Array Static Methods and More. (2021).