一句话定位:websockets 是 Python 里一套很克制、也很靠谱的 WebSocket 实现。它不想把自己做成“大而全的网络框架”,而是专注把长连接、双向消息、保活、背压、广播、自动重连这些实时通信里的脏活累活处理好。

很多 Python 项目一开始都只有普通 HTTP:
前期当然能跑。
但只要你的系统开始出现下面这些需求,轮询就会越来越别扭:
这时候问题就不再是“HTTP 能不能凑合干”,而是:
你到底需不需要一条真正持续存在的双向通道。
websockets 的价值就在这里。
它把很多人脑子里“实时通信很复杂”的那一坨,尽量压缩成了几件直观的事:
如果你最近在做这些东西,websockets 值得认真补上:

从当前安装包元数据可以直接确认:本文写作时可用的 websockets 版本为 16.0,要求 Python >= 3.10。官方在 README 里的定位也很明确:它是一个 for building WebSocket servers and clients in Python 的库,强调 correctness、simplicity、robustness、performance。
这几个词不是营销话术,基本就是它的产品哲学:
真正重要的变化,其实是这件事:

如果只用一句话概括:
websockets = 一套专注于 WebSocket 协议本身的 Python 实现,默认主打 asyncio,也提供 threading 和 Sans-I/O 版本。
官方 README 里点得很直白:
这意味着它不是只能服务某一种写法:

但它同样有明确边界。
官方 README 也明确提醒了:websockets 的目标是把 RFC 6455 WebSocket 协议 和压缩扩展实现好,HTTP 支持只有最小必要程度,甚至文档里直接说如果你要做“HTTP + WebSocket 混合服务”,更适合看基于它构建的服务器,比如 uvicorn、Sanic 这类框架。
这个边界非常重要。
很多人一上来就容易误判:
其实不是。
它更像一把很锋利的专用刀:
先看判断表:

我的判断是:
一句话:
websockets 适合做实时通道,不适合独自扛完整 Web 应用。
先安装:
pip install websockets
很多旧教程还在写顶层 websockets.serve(...)、websockets.connect(...)。当前官方 Quick examples 已经明显更偏向下面这种命名空间写法:
import asyncio
from websockets.asyncio.server import serve
async def echo(websocket):
async for message in websocket:
await websocket.send(f"echo: {message}")
async def main() -> None:
async with serve(echo, "127.0.0.1", 8765):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
再写一个客户端:
import asyncio
from websockets.asyncio.client import connect
async def main() -> None:
async with connect("ws://127.0.0.1:8765") as websocket:
await websocket.send("hello")
print(await websocket.recv())
if __name__ == "__main__":
asyncio.run(main())
这两段代码的价值不是“能不能做个 demo”,而是它把心智模型讲得很清楚:
我这里还用当前安装版本实际跑过一遍最小回声示例,serve(...) + connect(...) + send/recv 这套写法在 websockets 16.0 下是通的。
从当前版本的签名可以直接看到,serve(...) 最核心的参数还是一个 handler:
from websockets.asyncio.server import serve
async def handler(websocket):
async for message in websocket:
await websocket.send(message)
这个模型很值钱,因为它让你写实时服务时不必先去理解一大坨回调。
你可以把它理解成:
官方 serve() 的文档字符串里也明确写了这套生命周期。
它带来的最大好处就是:连接生命周期、协议握手和你的业务处理,被切成了相对清晰的几层。
connect(...) 也不是“连上就完”的小工具。
当前版本文档字符串里明确提到两种用法:
async with connect("ws://127.0.0.1:8765") as websocket:
await websocket.send("hello")
import websockets
from websockets.asyncio.client import connect
async def listen_forever() -> None:
async for websocket in connect("ws://127.0.0.1:8765/stream"):
try:
async for message in websocket:
print(message)
except websockets.exceptions.ConnectionClosed:
continue
官方说明里还点到了:
这对真实项目特别有用。
因为现实世界的连接并不会永远稳定:
如果客户端重连机制要你自己手搓一整套,工程体验会立刻变差很多。
只看 serve(...) 和 connect(...) 的签名,你就能感觉到它不是只为 demo 准备的。
当前版本里你能直接看到这些参数:
这些参数背后分别在处理几件现实问题:

官方 keepalive 文档也强调了一点:WebSocket 本来就是长连接,而 HTTP/1.1 基础设施往往并不天然欢迎“长时间空闲但不断着”的连接。
文档里直接提到,很多代理会在 30~120 秒 内关闭空闲连接;也就是说,如果你完全不做保活,你以为自己“连着”,中间网络设备未必同意。
所以别把 ping_interval 和 ping_timeout 看成无关紧要的小参数。
它们关系到的是:
你的实时系统到底能不能尽快发现“其实早就断了”。
很多实时系统最终都会落到一个经典需求:
websockets 当前版本直接提供了 broadcast(...):
from websockets.asyncio.server import ServerConnection, broadcast
CLIENTS: set[ServerConnection] = set()
def push_to_all(message: str) -> None:
if CLIENTS:
broadcast(CLIENTS, message)
这很方便。
我这里也实际跑过一个最小广播例子:维护一个连接集合,然后 broadcast(CLIENTS, "tick"),两个客户端都能正常收到消息。
但官方文档字符串同样把边界写得很明白:
这个提醒非常重要。
也就是说:
假设你现在有一个播客处理流水线:
这种场景如果继续靠轮询,通常会有几个问题:
更顺的方式,是把“任务进度变化”直接推给在线观察者。
import asyncio
import json
from datetime import datetime
from websockets.asyncio.server import ServerConnection, broadcast, serve
CLIENTS: set[ServerConnection] = set()
def make_event(job_id: str, step: str, progress: int) -> str:
return json.dumps(
{
"type": "job.progress",
"job_id": job_id,
"step": step,
"progress": progress,
"ts": datetime.now().isoformat(timespec="seconds"),
},
ensure_ascii=False,
)
async def handler(websocket: ServerConnection) -> None:
CLIENTS.add(websocket)
try:
await websocket.send(
json.dumps(
{
"type": "system.hello",
"online_clients": len(CLIENTS),
},
ensure_ascii=False,
)
)
async for _ in websocket:
# 这里可以接收客户端命令,例如暂停、继续、重试
pass
finally:
CLIENTS.discard(websocket)
async def fake_job_stream() -> None:
for progress, step in [(5, "queued"), (20, "transcoding"), (55, "denoise"), (80, "subtitle"), (100, "done")]:
await asyncio.sleep(1)
if CLIENTS:
broadcast(CLIENTS, make_event("podcast-20260406", step, progress))
async def main() -> None:
async with serve(handler, "127.0.0.1", 8765, ping_interval=20, ping_timeout=20):
while True:
await fake_job_stream()
await asyncio.sleep(2)
if __name__ == "__main__":
asyncio.run(main())
这个骨架有几个点值得注意:

上面这张图真正想表达的是:WebSocket 最值钱的不是“连接能不断着”,而是它把任务事件、连接管理、保活检测和客户端展示串成了一条稳定的实时链路。
import asyncio
import json
import websockets
from websockets.asyncio.client import connect
async def listen() -> None:
async for websocket in connect("ws://127.0.0.1:8765"):
try:
async for raw in websocket:
event = json.loads(raw)
print(event)
except websockets.exceptions.ConnectionClosed:
continue
if __name__ == "__main__":
asyncio.run(listen())
这段代码很适合做:
它背后的思路也很简单:
不要把 WebSocket 当成“某次特殊请求”,而要把它当成“持续到来的事件流”。
这是最容易犯的错误。
websockets 非常适合做实时消息通道,但它不是拿来承包:
如果你的项目本来就是 Web 服务,常见的更稳组合其实是:
websockets 可以单独用,也可以作为更大系统中的“实时管道”存在。
很多 demo 一开始都这么写:
await websocket.send("progress=40")
演示可以,工程里不建议。
更稳的方式几乎总是:
{
"type": "job.progress",
"job_id": "podcast-20260406",
"progress": 40,
"step": "transcoding"
}
至少要明确:
否则系统一复杂,你很快就会进入“前后端都在猜你这串文本到底什么意思”的状态。
broadcast() 好用,但官方已经提醒了它没有背压。
所以生产里最好遵守几个原则:
能用 1 条消息表达的状态,不要拆成 10 条碎片消息乱飞。
官方 keepalive 文档明确指出,很多中间网络设施会在 30~120 秒 关闭空闲连接。
所以生产里至少要想清楚:
如果这些问题完全不设计,系统上线后最常见的现象就是:
用户以为页面还连着,服务端以为连接还活着,但中间网络早就把它掐掉了。
只要是长连接,断线就不是意外,而是常态。
因此你要尽量让这些事情从第一天就有设计:
别等到线上出现“刷新一下页面进度没了”“断网再连回来就卡死”才想起这些。
websockets 最值得学的,不是“我也能写个 echo demo 了”,而是下面这套判断:
如果你最近正在做:
那 websockets 很可能就是那块把系统体验从“能用”拉到“顺手”的关键拼图。