Skip to content

详细介绍一下 HTTP 与 WebSocket 的区别(适当结合 Python 代码和生活实例)


一、生活类比:两种沟通方式

HTTP —— 写信

你给朋友写一封信(请求),朋友收到后回一封信(响应),然后这次通信就结束了。如果你还想聊,得再写一封新信,重新贴邮票、重新投递。

你:[写信] "明天几点出发?"  ──寄出──>  朋友
你:                         <──回信──  朋友:[回信] "早上 9 点"
(通信结束,信封销毁)

你:[再写一封信] "在哪集合?" ──寄出──>  朋友
你:                          <──回信──  朋友:[回信] "地铁站"
(通信又结束了)

特点:每次都要重新建立连接,朋友不能主动给你写信(除非你先发起)。

WebSocket —— 打电话

你给朋友拨通电话(握手),接通后双方可以随时说话,不用每次都重新拨号。想说什么直接说,朋友也能随时主动告诉你新消息。

你:[拨号] ──────────────>  朋友:[接听]
(连接建立,保持通话)

你:"明天几点出发?"
                           朋友:"早上 9 点"
                           朋友:"对了,记得带伞,明天有雨"  ← 朋友主动说的!
你:"好的,谢谢"
...
你:[挂断]
(连接关闭)

特点:一次连接,持续双向通信,任何一方都能主动发消息。


二、技术对比

维度HTTPWebSocket
通信模式请求-响应(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 /chatHTTP发送一条消息,得到确认脚本测试、curl 调试、一次性指令
GET /wsWebSocket建立长连接,实时收发前端聊天界面、需要实时收到 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 就够了。