python websockets
# 聊聊Python WebSocket从入门到不踩坑几年前我第一次用WebSocket做实时推送时和大多数人一样第一反应是“这不就是长轮询的升级版吗” 后来才慢慢体会到这两者之间的差距比自行车和摩托车的差距要更大一些——自行车你还能用脚蹬两下救个急WebSocket和HTTP的关系更像是电话和书信。一个是实时对话一个是寄出去等回信。它到底是什么WebSocket本质上是HTTP的一次升级。确切地说它利用HTTP协议发起“握手”然后双方协商升级到WebSocket协议。之后数据就不用再像HTTP那样每次都要带上那套冗长的请求头了。用一个更具体的例子来描述想象你在网上聊天对方说一句你回一句。如果只有HTTP你得每说一句话就新建一条“线路”每次还要告诉对方你是谁、用的是什么类型的对话方式、能接受什么内容——哪怕你上一秒刚问过“你好”。WebSocket的做法是先握个手“你好我们要用WebSocket聊天了”接下来双方只需要来回发消息本身就行了。在Python生态里最常用的两个库是websockets注意是带s的和由FastAPI团队维护的websockets的竞争对手——准确地说很多人会在FastAPI里直接使用它的WebSocket支持底层可能是websockets或uvicorn。它能做什么WebSocket最自然的应用场景是那些“服务器主动找客户端”的场景。举几个我实际做过的项目实时股票行情推送——客户端不需要每隔一秒问一次“价格变了没”多人协作编辑——别人改了哪一行代码你这边立刻看到黄色高亮游戏房间——对方出了牌你的牌面上立刻消失那张牌设备监控面板——机器温度超标页面直接弹红色警告有一类需要注意的场景是“上万人订阅同一个广播”。理论上WebSocket能做到但实际上在Python里要特别小心。Python的全局解释器锁GIL会让你的事件循环在发大量消息时出现瓶颈。后面会讲怎么处理。还有一个容易混淆的很多人觉得消息推送、即时通知就应该上WebSocket。但如果你只是“每隔30秒推送一次当前天气”用SSEServer-Sent Events会简单很多。这个在最后对比部分会详细说。怎么使用先用最简单的websockets库写个入门。假设我们有个需求客户端发来一个数字服务器返回这个数的平方。服务器端importasyncioimportwebsocketsasyncdefsquare(websocket,pathNone):asyncformessageinwebsocket:try:numberint(message)awaitwebsocket.send(f结果是{number*number})exceptValueError:awaitwebsocket.send(请发送数字)start_serverwebsockets.serve(square,localhost,8765)asyncio.get_event_loop().run_until_complete(start_server)asyncio.get_event_loop().run_forever()客户端importasyncioimportwebsocketsasyncdefask():asyncwithwebsockets.connect(ws://localhost:8765)asws:awaitws.send(5)responseawaitws.recv()print(response)asyncio.get_event_loop().run_until_complete(ask())这段代码很直白但有几处可能被忽略路径参数——在老版本里path是必需的新版本里已经改成websocket.serve统一用路由了。如果你用新版本10以上可以写成websockets.serve(square, ...)然后不传path参数但建议还是显式写明。async for循环——一旦连接断开这个循环会自动结束不会抛出WebSocket异常。这一点比手动监听recv好不知道多少倍。异常处理——如果客户端突然断连你试图await websocket.send会抛异常。你需要全局捕获websockets.ConnectionClosed。用FastAPI的场景更常见一点fromfastapiimportFastAPI,WebSocket,WebSocketDisconnect appFastAPI()app.websocket(/ws)asyncdefwebsocket_endpoint(websocket:WebSocket):awaitwebsocket.accept()try:whileTrue:dataawaitwebsocket.receive_text()awaitwebsocket.send_text(f你说了:{data})exceptWebSocketDisconnect:print(客户端离开了)这样写的好处是FastAPI帮你处理了底层协议和依赖注入。缺点也很明显如果你只是做WebSocket服务引入FastAPI和Uvicorn有点overkill。最佳实践并发瓶颈问题Python WebSocket在单进程里处理几千个连接通常没问题但如果每个连接都要频繁发消息问题就来了。一个常见的优化是不要在连接对象上直接发消息而是用发布-订阅模式。举个例子假设股市行情每隔0.5秒更新一次有5000个客户端订阅“AAPL”股票。最粗暴的做法是forclientinclients:awaitclient.send(data)每次更新花了30毫秒这就占了服务器6%的处理时间。更好的做法是分批次处理把5000个连接分成几组一次发一组每隔几毫秒发下一组。甚至可以考虑用asyncio.gather来并发发送但要小心太多并发同时做IO反而更慢。还有一种更极端的办法如果数据量大可以把WebSocket服务单独部署用Redis做消息通道Python进程只负责转换消息格式实际推送交给Go或Rust实现的网关层。心跳机制WebSocket规范里有ping/pong帧但很多浏览器库不主动发ping。这导致一个问题如果客户端网络断了但没触发TCP断开比如手机进电梯服务器可能永远不知道它走了。我通常在服务器端每隔30秒给所有客户端发一个简单的ping如果连续两次收不到pong就认为连接死亡主动关闭并清理资源。websockets库有ping()方法但是要注意await websocket.ping()是异步的你需要单独用一个协程来管理所有心跳。优雅关闭在服务器关闭时如果不做处理所有客户端会瞬间收到ConnectionResetError。更好的做法是先向所有客户端广播一条“服务器即将关闭”的消息然后等待1-2秒再关闭所有连接。不要信任客户端输入的size如果有人往你的WebSocket里发一个几百兆的字符串你的服务器内存可能会爆炸。websockets库默认有max_size参数建议设置一个合理值比如websockets.serve(handler, ..., max_size65536)。同类技术对比短轮询最古老的方式。客户端每隔几秒发一次HTTP请求“有变化吗” “没有。” “有变化吗” “没有。”优点实现简单不需要额外库任何浏览器都支持。缺点浪费带宽有延迟取决于轮询间隔服务器压力大。适合场景个人小项目或者需求不要求实时的管理后台。长轮询客户端发请求服务器不立刻返回而是等到有数据了才返回。客户端收到后立刻发下一个请求。优点比短轮询实时性高带宽浪费少。缺点保持大量长连接会消耗服务器线程/进程资源。HTTP请求头每次都带着浪费流量。代码复杂度比短轮询高很多。适合场景浏览器不支持WebSocket的极端情况现在已经很少或者企业防火墙只允许HTTP。Server-Sent EventsSSE这个经常被忽略。它是单向的只能服务器主动发给客户端。优点走HTTP协议不需要额外升级。浏览器原生支持EventSourceAPI。自动重连你什么都不用写浏览器自己会重连。缺点只能发文本不能发二进制比如图片、音频。单向通信客户端不能直接发消息可以通过单独发一个HTTP POST来实现双向交互。SSE和WebSocket的判断标准很简单如果你只需要服务器推数据给客户端不需要客户端发复杂消息回来用SSE。比如实时股价、系统日志流、AI推理过程输出。如果双方有频繁的双向通信聊天、游戏、协同编辑用WebSocket。Socket.IO这是WebSocket的“老朋友”了但其实它有自己的协议底层可以用WebSocket、长轮询等多种方式。Python有对应的python-socketio库。优点封装了自动重连、房间管理、事件命名空间等高级功能。如果客户端网络连接断开再恢复它能自动复现之前的订阅状态。缺点协议不是标准的WebSocket你需要用他家的客户端JS、Python、Java等。内部会做一些探测和协商比裸WebSocket多几轮握手。适合场景如果你需要跨平台、需要自动重连、又不想自己写太多基础设施代码Socket.IO是不错的选择。选择建议我的个人习惯是这样的做一个单功能的实时通知比如系统报警用SSE。做一个多功能的实时面板既有主动推送又有命令下发用裸WebSocket配合websockets或FastAPI。做一个跨平台的即时通讯App移动端、Web端、桌面端用Socket.IO。如果是个人练手项目轮询就够用了别想太多。结语WebSocket本身不复杂复杂的是它周围的那套工程实践——连接管理、心跳检测、优雅关闭、流量控制、安全性这个没展开但记得生产环境一定要用wss://加密。Python生态在这些方面都比较成熟只是想达到“高性能”还需要做一些底层优化。最后说个细节websockets库的异步模型比ws4py等老库成熟得多。如果现在要写生产环境的WebSocket服务基本是在websockets和FastAPI之间二选一。选择的关键不在于WebSocket本身而在于你的其他API是不是也用FastAPI——如果项目里已经有完整的FastAPI应用那没必要单独起一个WebSocket服务。反之一个纯粹的消息推送服务用轻量的websockets就够了。