主题
详细介绍一下 HTTP 与 WebSocket 的区别(适当结合 Python 代码和生活实例)
一、生活类比:两种沟通方式
HTTP —— 写信
你给朋友写一封信(请求),朋友收到后回一封信(响应),然后这次通信就结束了。如果你还想聊,得再写一封新信,重新贴邮票、重新投递。
你:[写信] "明天几点出发?" ──寄出──> 朋友
你: <──回信── 朋友:[回信] "早上 9 点"
(通信结束,信封销毁)
你:[再写一封信] "在哪集合?" ──寄出──> 朋友
你: <──回信── 朋友:[回信] "地铁站"
(通信又结束了)特点:每次都要重新建立连接,朋友不能主动给你写信(除非你先发起)。
WebSocket —— 打电话
你给朋友拨通电话(握手),接通后双方可以随时说话,不用每次都重新拨号。想说什么直接说,朋友也能随时主动告诉你新消息。
你:[拨号] ──────────────> 朋友:[接听]
(连接建立,保持通话)
你:"明天几点出发?"
朋友:"早上 9 点"
朋友:"对了,记得带伞,明天有雨" ← 朋友主动说的!
你:"好的,谢谢"
...
你:[挂断]
(连接关闭)特点:一次连接,持续双向通信,任何一方都能主动发消息。
二、技术对比
| 维度 | HTTP | WebSocket |
|---|---|---|
| 通信模式 | 请求-响应(Request-Response) | 全双工(Full-Duplex) |
| 连接方式 | 短连接,每次请求都要重新建立(HTTP/1.1 有 Keep-Alive 但仍是请求驱动) | 长连接,一次握手后持续保持 |
| 谁能主动发消息 | 只有客户端能发起请求 | 客户端和服务端都能随时发送 |
| 协议头开销 | 每次请求都携带完整的 HTTP 头(Cookie、User-Agent 等) | 握手后数据帧只有 2-10 字节头部 |
| 实时性 | 差(需要轮询或长轮询来模拟) | 好(天然实时推送) |
| 适用场景 | 获取网页、提交表单、REST API | 聊天、实时通知、在线游戏、股票行情 |
连接建立过程
HTTP 请求(每次独立):
客户端 ──[TCP 三次握手]──> 服务端
客户端 ──[HTTP 请求]─────> 服务端
客户端 <──[HTTP 响应]───── 服务端
客户端 ──[TCP 四次挥手]──> 服务端(连接关闭)
WebSocket 连接:
客户端 ──[TCP 三次握手]──────────> 服务端
客户端 ──[HTTP Upgrade 请求]────> 服务端 ← 先用 HTTP 握手
客户端 <──[101 Switching Protocols]── 服务端 ← 协议升级
客户端 <══[双向数据帧持续传输]═══> 服务端 ← 升级为 WebSocket
...(保持连接,随时通信)...
客户端 ──[Close 帧]────────────> 服务端 ← 主动关闭WebSocket 的巧妙之处在于:它借用 HTTP 来完成握手,然后"升级"为自己的协议。就像你先打固定电话联系朋友,然后说"我们切到视频通话吧"。
三、Python 代码对比
3.1 HTTP 方式:一问一答
python
from aiohttp import web
async def handle_chat(request: web.Request) -> web.Response:
"""HTTP POST /chat —— 每次请求都是独立的"""
body = await request.json()
user_message = body.get("message", "")
# 处理消息,生成回复
reply = f"echo: {user_message}"
# 返回响应后,这次通信就结束了
# 如果服务端之后有新消息想告诉客户端?——做不到,只能等客户端再来问
return web.json_response({"reply": reply})客户端调用:
bash
# 每发一条消息就是一次独立的 HTTP 请求
curl -X POST http://localhost:8080/chat \
-H "Content-Type: application/json" \
-d '{"message": "你好"}'
# 返回: {"reply": "echo: 你好"}
# 想发第二条?再来一次请求
curl -X POST http://localhost:8080/chat \
-H "Content-Type: application/json" \
-d '{"message": "第二条消息"}'3.2 WebSocket 方式:持续对话
python
from aiohttp import web
import json
async def handle_websocket(request: web.Request) -> web.WebSocketResponse:
"""WebSocket /ws —— 连接建立后,双方持续通信"""
ws = web.WebSocketResponse()
await ws.prepare(request) # 完成握手,连接建立
# 连接保持打开,持续监听客户端消息
async for ws_msg in ws:
if ws_msg.type == web.WSMsgType.TEXT:
data = json.loads(ws_msg.data)
content = data.get("message", ws_msg.data)
# 随时可以向客户端推送消息——不需要等客户端"请求"
await ws.send_str(json.dumps({"reply": f"echo: {content}"}))
# 甚至可以主动推送额外信息
await ws.send_str(json.dumps({"notice": "这是服务端主动推送的通知"}))
return ws客户端调用(Python):
python
import aiohttp
import asyncio
async def chat():
async with aiohttp.ClientSession() as session:
async with session.ws_connect("http://localhost:8080/ws") as ws:
# 连接建立后,可以随时发消息
await ws.send_str('{"message": "你好"}')
# 持续接收服务端的推送(包括回复和主动通知)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
print(f"收到: {msg.data}")
elif msg.type == aiohttp.WSMsgType.CLOSED:
break
asyncio.run(chat())3.3 关键区别一眼看
python
# HTTP:函数执行完就断开,客户端拿到响应后连接结束
async def http_handler(request):
data = await request.json() # 1. 收到请求
result = process(data) # 2. 处理
return web.json_response(result) # 3. 返回响应,连接结束 ←
# WebSocket:函数内部是一个循环,连接一直保持
async def ws_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request) # 1. 建立连接
async for msg in ws: # 2. 持续监听 ← 循环!
result = process(msg.data) # 3. 处理
await ws.send_str(result) # 4. 推送回去,但连接不断开
return ws # 5. 客户端断开后才走到这里四、在 miniOpenClaw 中的应用
我们的 WebChatChannel 同时支持了两种方式,正好体现了它们的不同用法:
| 端点 | 协议 | 用途 | 适合场景 |
|---|---|---|---|
POST /chat | HTTP | 发送一条消息,得到确认 | 脚本测试、curl 调试、一次性指令 |
GET /ws | WebSocket | 建立长连接,实时收发 | 前端聊天界面、需要实时收到 Agent 回复 |
两者收到的消息最终都放入同一个 asyncio.Queue,由 receive() 统一消费:
python
# webchat.py 中的设计
self._message_queue: asyncio.Queue[GatewayMessage] = asyncio.Queue()
# HTTP 和 WebSocket 都往同一个队列里放消息
async def _handle_chat(self, request): # HTTP
msg = GatewayMessage.text(content=user_message, ...)
await self._message_queue.put(msg) # ← 放入队列
async def _handle_websocket(self, request): # WebSocket
async for ws_msg in ws:
msg = GatewayMessage.text(content=content, ...)
await self._message_queue.put(msg) # ← 同一个队列
# 上层 Agent 不关心消息是从 HTTP 还是 WebSocket 来的
async def receive(self):
while self._running:
message = await self._message_queue.get() # 统一消费
yield message但回复方式不同——send() 只通过 WebSocket 推送。这意味着:
- 通过 WebSocket 连接的用户能实时收到 Agent 的回复
- 通过 HTTP POST 发送的用户只拿到一个
msg_id确认,需要另外建立 WebSocket 连接来接收回复(或者改造成同步等待响应)
这正是两种协议特性的体现:HTTP 天然不支持服务端主动推送,WebSocket 则可以。
五、什么时候该用哪个?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 用户提交表单 | HTTP | 一次性操作,不需要保持连接 |
| REST API(CRUD) | HTTP | 无状态、幂等,适合资源操作 |
| 在线聊天 | WebSocket | 需要实时双向通信 |
| 实时通知 / 弹幕 | WebSocket | 服务端需要主动推送 |
| 文件上传/下载 | HTTP | 请求-响应模式足够,且有成熟的断点续传方案 |
| 股票行情 / 实时数据 | WebSocket | 数据高频更新,HTTP 轮询开销太大 |
| Agent 流式输出(逐字生成) | WebSocket | 需要持续推送部分结果 |
一句话总结:需要"服务端主动推送"或"高频双向通信"时用 WebSocket,其他场景 HTTP 就够了。