漫扯:从polling到Websocket 漫扯从 Polling 到 WebSocket在互联网的早期服务器与客户端之间的通信主要依赖于“请求-响应”模式客户端发送请求服务器返回响应。然而随着实时应用如聊天、股票行情推送、协作编辑的兴起这种模式显得捉襟见肘——它无法让服务器主动向客户端推送数据。为了解决这个问题开发者们发明了各种“变通”方案其中最具代表性的就是Polling轮询和WebSocket。本文将带你从底层原理出发逐步揭开从轮询到 WebSocket 的进化之路。## 一、Polling一种“笨拙”的实时方案### 1.1 短轮询Short Polling短轮询是最简单的“实时”模拟方案。客户端每隔固定时间如1秒向服务器发送 HTTP 请求询问是否有新数据。服务器无论是否有新数据都会立即响应。工作原理- 客户端setInterval(() fetch(/data), 1000)- 服务器接收到请求后检查数据是否更新返回结果。问题- 浪费带宽许多请求返回的是“无新数据”但完整 HTTP 头部仍被传输。- 延迟不可控假设数据在第 500ms 更新客户端需要等到下一轮请求第 1000ms才能获取平均延迟为轮询间隔的一半。- 服务器压力大量短时连接会造成服务器资源浪费。### 1.2 长轮询Long Polling为了解决短轮询的延迟问题长轮询应运而生。客户端发送请求后服务器会挂起该连接直到有新数据时才返回。如果长时间无数据服务器会返回超时响应客户端立即重新发起请求。工作原理- 客户端发送请求后不立即断开连接而是等待服务器响应。- 服务器收到请求后如果无新数据则阻塞连接不返回直到数据可用或超时。- 一旦返回客户端立即再次发起请求。优点延迟大大降低数据产生后即可返回。缺点- 仍然需要频繁建立和关闭连接即使使用了 Keep-Alive。- 服务器需要维护大量挂起的连接消耗内存和线程资源。- 无法实现真正的双向通信服务器只能在响应中推送数据。### 代码示例短轮询 vs 长轮询Python 伪代码python# 示例1短轮询的客户端与服务器实现基于 Flask requests# 注意此代码仅为演示原理非生产级实现import timeimport threadingfrom flask import Flask, jsonify, requestimport requestsapp Flask(__name__)# 模拟共享数据假设由另一个线程更新data_store {value: 初始数据}lock threading.Lock()# 模拟数据更新线程def update_data(): global data_store time.sleep(5) # 5秒后更新数据 with lock: data_store[value] 新数据app.route(/short_poll, methods[GET])def short_poll(): 短轮询端点直接返回当前数据 with lock: return jsonify({data: data_store[value]})# 客户端模拟def client_short_poll(): for i in range(10): resp requests.get(http://127.0.0.1:5000/short_poll) print(f第{i1}次轮询: {resp.json()}) time.sleep(1) # 每1秒轮询一次# 启动数据更新线程threading.Thread(targetupdate_data, daemonTrue).start()if __name__ __main__: # 启动客户端线程仅演示 threading.Thread(targetclient_short_poll, daemonTrue).start() app.run(port5000)长轮询的简化实现仅展示核心差异点python# 示例2长轮询的服务器端使用 Flask模拟阻塞# 注意Flask 默认是同步的挂起连接会阻塞线程生产环境需使用异步框架from flask import Flask, jsonify, requestimport timeapp Flask(__name__)# 模拟数据初始为 None等待外部设置data Noneapp.route(/long_poll, methods[GET])def long_poll(): 长轮询端点如果没有新数据则等待最多30秒 global data timeout 30 # 超时时间秒 start_time time.time() # 模拟等待新数据实际场景中应使用事件队列、条件变量等 while time.time() - start_time timeout: if data is not None: # 返回数据并清空缓存 response jsonify({data: data}) data None return response # 休眠一小段时间避免忙等待CPU 空转 time.sleep(0.5) # 超时返回空响应 return jsonify({error: timeout}), 204if __name__ __main__: app.run(port5000)## 二、WebSocket真正的全双工通信### 2.1 原理剖析WebSocket 是 HTML5 引入的协议RFC 6455它基于 HTTP 进行握手然后升级为独立的 TCP 连接实现客户端与服务器之间的全双工通信。握手过程1. 客户端发送 HTTP 请求包含Upgrade: websocket和Sec-WebSocket-Key随机字符串。2. 服务器计算Sec-WebSocket-Accept基于 Key 和固定 GUID 的 SHA-1 哈希返回101 Switching Protocols。3. 连接升级成功之后双方可以互相发送帧Frame数据。帧结构WebSocket 数据以帧为单位传输包含操作码如文本帧、二进制帧、关闭帧、掩码客户端到服务器必须掩码、有效载荷长度等。优势-低延迟数据产生后服务器可立即推送无需等待客户端请求。-低开销建立连接后不再需要 HTTP 头部帧头仅 2-14 字节。-双向通信客户端和服务器都可以主动发送消息。### 2.2 代码示例使用 Python 实现 WebSocket 服务器python# 示例3基于 websockets 库的 WebSocket 服务器与客户端# 安装pip install websocketsimport asyncioimport websockets# 存储连接的客户端connected_clients set()async def handler(websocket, path): 处理每个 WebSocket 连接 # 将新客户端加入集合 connected_clients.add(websocket) try: async for message in websocket: # 收到客户端消息后广播给所有其他客户端 print(f收到消息: {message}) # 回复客户端回声 await websocket.send(f服务器已收到: {message}) # 同时广播给其他客户端模拟实时聊天 for client in connected_clients: if client ! websocket: await client.send(f广播: {message}) except websockets.ConnectionClosed: print(客户端断开连接) finally: # 移除断开的客户端 connected_clients.remove(websocket)async def main(): # 启动 WebSocket 服务器监听本地 8765 端口 async with websockets.serve(handler, localhost, 8765): print(WebSocket 服务器已启动地址: ws://localhost:8765) await asyncio.Future() # 保持服务器运行if __name__ __main__: asyncio.run(main())客户端示例浏览器或 Python 均可python# 示例4Python WebSocket 客户端import asyncioimport websocketsasync def chat(): uri ws://localhost:8765 async with websockets.connect(uri) as websocket: # 发送消息 await websocket.send(Hello, 服务器!) # 接收响应 response await websocket.recv() print(f服务器回复: {response})asyncio.run(chat())## 三、从 Polling 到 WebSocket技术演进总结| 特性 | 短轮询 | 长轮询 | WebSocket ||--------------|----------------------|----------------------|---------------------|| 通信方向 | 客户端→服务器 | 客户端→服务器可延迟响应 | 全双工双向实时 || 延迟 | 高~轮询间隔/2 | 低数据产生后立即返回 | 极低无额外延迟 || 带宽开销 | 高频繁 HTTP 请求 | 中连接挂起但仍需头部 | 低帧头极小 || 服务器资源 | 轻量连接短暂 | 重量需维护大量挂起连接 | 轻量长连接异步处理 || 实现复杂度 | 简单 | 中等需处理超时和状态 | 中等需升级握手 |## 总结从短轮询到长轮询再到 WebSocket这不仅是技术的演变更是对“实时性”需求的回应。短轮询如同一个不断打电话问“有消息吗”的人长轮询像是“你挂电话等有消息再打给我”而 WebSocket 则是“电话接通后我们可以随时说话”。虽然 Polling 在特定场景下仍有价值如简单的状态检查但 WebSocket 已成为实时应用的事实标准。理解它们的原理能帮助我们在面对不同的业务需求时做出更明智的技术选型。