WebSocket 双向通信完整教程
WebSocket 双向通信完整教程
从零开始理解 WebSocket,通过两个完整示例掌握核心概念
📚 目录
什么是 WebSocket?
传统 HTTP 的局限
HTTP 协议是单向的:
客户端发起请求 → 服务端响应 → 连接关闭
问题:
- ❌ 服务端不能主动推送消息
- ❌ 需要轮询(客户端不停问"有新消息吗?")
- ❌ 每次请求都要重新建立连接(开销大)
WebSocket 的优势
WebSocket 是全双工通信协议:
客户端 ←→ 服务端(连接保持,双方随时收发)
优势:
- ✅ 服务端可以主动推送消息
- ✅ 连接持久化(不需要反复建立连接)
- ✅ 实时性高(延迟低)
- ✅ 带宽占用少
典型应用场景
| 场景 | 说明 |
|---|---|
| 即时聊天 | 微信、Slack 等 |
| 实时数据 | 股票行情、体育比分 |
| 在线游戏 | 多人实时同步 |
| 协作编辑 | Google Docs、腾讯文档 |
| 通知推送 | 系统通知、消息提醒 |
示例 1:基础通信 - 客户端请求,服务端响应
场景描述
客户端主动发送消息,服务端接收后原样返回(Echo 模式)。
代码结构
d2/
├── simple_server.py # 服务端
├── simple_client.py # 客户端
└── requirements.txt # 依赖
安装依赖
pip install websockets==12.0
服务端代码 (simple_server.py)
"""
最简单的 WebSocket 服务端
功能:接收客户端消息,原样返回
"""
import asyncio
import websockets
# 处理每个客户端连接的函数
async def handle_client(websocket):
print("有客户端连接了!")
# 持续监听客户端发来的消息
async for message in websocket:
print(f"收到消息: {message}")
# 把消息原样发回去
await websocket.send(f"服务端收到: {message}")
# 主函数
async def main():
# 在本地 8765 端口启动服务
print("WebSocket 服务端启动在 ws://localhost:8765")
async with websockets.serve(handle_client, "localhost", 8765):
await asyncio.Future() # 让服务一直运行
if __name__ == "__main__":
asyncio.run(main())
代码解析:
| 代码 | 说明 |
|---|---|
async def handle_client(websocket) |
定义处理函数,每个客户端连接时调用 |
async for message in websocket |
循环监听消息,客户端断开时自动结束 |
await websocket.send() |
发送消息给客户端 |
websockets.serve() |
启动服务端,监听指定地址和端口 |
await asyncio.Future() |
保持服务运行,不退出 |
客户端代码 (simple_client.py)
"""
最简单的 WebSocket 客户端
功能:连接服务端,发送消息,接收回复
"""
import asyncio
import websockets
async def main():
# 连接到服务端
uri = "ws://localhost:8765"
print(f"正在连接 {uri}...")
async with websockets.connect(uri) as websocket:
print("连接成功!")
# 发送 3 条消息测试
for i in range(1, 4):
message = f"这是第 {i} 条消息"
print(f"发送: {message}")
await websocket.send(message)
# 等待服务端回复
response = await websocket.recv()
print(f"收到回复: {response}")
await asyncio.sleep(1) # 等待 1 秒再发下一条
print("测试完成!")
if __name__ == "__main__":
asyncio.run(main())
代码解析:
| 代码 | 说明 |
|---|---|
websockets.connect(uri) |
连接到服务端 |
await websocket.send() |
发送消息给服务端 |
await websocket.recv() |
等待接收服务端消息(阻塞) |
async with |
自动管理连接,退出时自动关闭 |
运行步骤
终端 1 - 启动服务端:
cd d2
python simple_server.py
终端 2 - 运行客户端:
cd d2
python simple_client.py
运行结果
服务端输出:
WebSocket 服务端启动在 ws://localhost:8765
有客户端连接了!
收到消息: 这是第 1 条消息
收到消息: 这是第 2 条消息
收到消息: 这是第 3 条消息
客户端输出:
正在连接 ws://localhost:8765...
连接成功!
发送: 这是第 1 条消息
收到回复: 服务端收到: 这是第 1 条消息
发送: 这是第 2 条消息
收到回复: 服务端收到: 这是第 2 条消息
发送: 这是第 3 条消息
收到回复: 服务端收到: 这是第 3 条消息
测试完成!
时序图
客户端 服务端
| |
|--- connect ------------------>| 建立连接
| |
|--- "这是第 1 条消息" --------->|
|<-- "服务端收到: ..." ---------|
| |
|--- "这是第 2 条消息" --------->|
|<-- "服务端收到: ..." ---------|
| |
|--- "这是第 3 条消息" --------->|
|<-- "服务端收到: ..." ---------|
| |
|--- close -------------------->| 关闭连接
关键点
- 必须先启动服务端,客户端才能连接
- 一问一答模式:客户端发送 → 等待回复 → 再发送
- 这是最基础的 WebSocket 通信模式
示例 2:服务端主动推送
场景描述
服务端可以不等客户端请求,主动推送消息。同时客户端也可以发送消息,实现真正的全双工通信。
代码结构
d2/
├── push_server.py # 服务端(主动推送)
└── push_client.py # 客户端(同时收发)
服务端代码 (push_server.py)
"""
服务端主动推送消息示例
功能:
1. 客户端连接后,服务端主动发送欢迎消息
2. 每隔 3 秒,服务端主动推送一条消息
3. 同时还能接收并回复客户端的消息
"""
import asyncio
import websockets
from datetime import datetime
async def handle_client(websocket):
"""处理每个客户端"""
print(f"[{datetime.now():%H:%M:%S}] 客户端已连接")
# 1. 服务端主动发送欢迎消息
welcome = "🎉 欢迎连接!我是服务端,我会每 3 秒给你推送消息"
await websocket.send(welcome)
print(f"[{datetime.now():%H:%M:%S}] 已发送欢迎消息")
try:
# 同时运行两个任务:定时推送 + 接收消息
await asyncio.gather(
push_periodic_messages(websocket),
receive_client_messages(websocket)
)
except Exception as e:
print(f"[{datetime.now():%H:%M:%S}] 客户端断开连接")
async def push_periodic_messages(websocket):
"""定时推送消息给客户端"""
count = 1
while True:
await asyncio.sleep(3) # 每 3 秒
# 服务端主动推送(不需要客户端先发消息)
message = f"📢 [服务端推送 #{count}] 现在时间是 {datetime.now():%H:%M:%S}"
await websocket.send(message)
print(f"[{datetime.now():%H:%M:%S}] 推送消息给客户端")
count += 1
async def receive_client_messages(websocket):
"""接收客户端消息"""
async for message in websocket:
print(f"[{datetime.now():%H:%M:%S}] 收到客户端消息: {message}")
# 回复客户端
reply = f"✅ 服务端收到: {message}"
await websocket.send(reply)
async def main():
host = "localhost"
port = 8765
print(f"WebSocket 服务端启动在 ws://{host}:{port}")
print("等待客户端连接...")
async with websockets.serve(handle_client, host, port):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
核心亮点:
# 🔥 关键代码:服务端主动推送
async def push_periodic_messages(websocket):
while True:
await asyncio.sleep(3)
await websocket.send(推送内容) # 主动发送,不等客户端请求!
客户端代码 (push_client.py)
"""
接收服务端推送消息的客户端
功能:
1. 连接后持续监听服务端的推送
2. 同时可以向服务端发送消息
"""
import asyncio
import websockets
async def receive_messages(websocket):
"""持续接收服务端的消息"""
print("\n========== 接收服务端消息 ==========")
try:
async for message in websocket:
# 服务端推送的消息会在这里收到
print(f"\n📩 收到: {message}")
print(">>> ", end="", flush=True)
except:
print("\n连接已断开")
async def send_messages(websocket):
"""发送消息给服务端"""
print("\n========== 你可以输入消息 ==========")
print("输入任意文字发送给服务端,输入 'quit' 退出\n")
loop = asyncio.get_event_loop()
while True:
print(">>> ", end="", flush=True)
user_input = await loop.run_in_executor(None, input)
if user_input.strip().lower() == "quit":
print("正在断开连接...")
break
if user_input.strip():
await websocket.send(user_input.strip())
async def main():
uri = "ws://localhost:8765"
print(f"正在连接 {uri}...")
try:
async with websockets.connect(uri) as websocket:
print("✅ 连接成功!")
# 🔥 关键:同时运行接收和发送两个任务
receive_task = asyncio.create_task(receive_messages(websocket))
send_task = asyncio.create_task(send_messages(websocket))
# 等待任意一个任务结束
done, pending = await asyncio.wait(
[receive_task, send_task],
return_when=asyncio.FIRST_COMPLETED
)
# 取消未完成的任务
for task in pending:
task.cancel()
except ConnectionRefusedError:
print("❌ 连接失败!请确保服务端已启动。")
except Exception as e:
print(f"❌ 错误: {e}")
print("已断开连接。")
if __name__ == "__main__":
asyncio.run(main())
核心亮点:
# 🔥 关键代码:同时收发(全双工)
await asyncio.gather(
receive_messages(websocket), # 后台持续接收推送
send_messages(websocket) # 前台等待用户输入
)
运行步骤
终端 1 - 启动服务端:
cd d2
python push_server.py
终端 2 - 运行客户端:
cd d2
python push_client.py
运行结果
客户端输出:
正在连接 ws://localhost:8765...
✅ 连接成功!
========== 接收服务端消息 ==========
========== 你可以输入消息 ==========
输入任意文字发送给服务端,输入 'quit' 退出
📩 收到: 🎉 欢迎连接!我是服务端,我会每 3 秒给你推送消息
>>>
📩 收到: 📢 [服务端推送 #1] 现在时间是 14:23:05
>>> hello
📩 收到: ✅ 服务端收到: hello
>>>
📩 收到: 📢 [服务端推送 #2] 现在时间是 14:23:08
>>> 你好
📩 收到: ✅ 服务端收到: 你好
>>>
📩 收到: 📢 [服务端推送 #3] 现在时间是 14:23:11
>>> quit
正在断开连接...
已断开连接。
服务端输出:
WebSocket 服务端启动在 ws://localhost:8765
等待客户端连接...
[14:23:02] 客户端已连接
[14:23:02] 已发送欢迎消息
[14:23:05] 推送消息给客户端
[14:23:06] 收到客户端消息: hello
[14:23:08] 推送消息给客户端
[14:23:09] 收到客户端消息: 你好
[14:23:11] 推送消息给客户端
[14:23:12] 客户端断开连接
时序图
时间 客户端 服务端
-----|--------------------------------|--------------------------------
0s |--- connect ------------------->| 建立连接
|<-- 欢迎消息 -------------------|(主动推送)
3s |<-- 推送 #1 --------------------|(主动推送,不需要请求)
5s |--- "hello" ------------------->|
|<-- "服务端收到: hello" --------|
6s |<-- 推送 #2 --------------------|(主动推送)
8s |--- "你好" -------------------->|
|<-- "服务端收到: 你好" ---------|
9s |<-- 推送 #3 --------------------|(主动推送)
12s |--- quit ----------------------->| 关闭连接
关键点
- 服务端主动推送:每 3 秒自动发送,不需要客户端请求
- 全双工通信:推送和对话同时进行,互不干扰
- 异步并发:客户端同时运行两个任务(接收 + 发送)
核心概念总结
1. async/await 异步编程
# 同步(阻塞)
def function():
result = blocking_operation() # 必须等待完成
return result
# 异步(非阻塞)
async def function():
result = await async_operation() # 等待但不阻塞其他任务
return result
作用:
- 一个程序可以同时处理多个任务
- 服务端可以同时处理多个客户端
- 客户端可以同时收发消息
2. WebSocket 核心 API
| API | 说明 | 示例 |
|---|---|---|
websockets.serve(handler, host, port) |
启动服务端 | 服务端使用 |
websockets.connect(uri) |
连接服务端 | 客户端使用 |
await websocket.send(msg) |
发送消息 | 双方都可用 |
await websocket.recv() |
接收一条消息(阻塞) | 双方都可用 |
async for msg in websocket |
持续接收消息(循环) | 双方都可用 |
3. 通信模式对比
| 模式 | 特点 | 适用场景 | 代码示例 |
|---|---|---|---|
| 请求-响应 | 客户端问,服务端答 | 查询类操作 | simple_server.py |
| 服务端推送 | 服务端主动发消息 | 通知、实时数据 | push_server.py |
| 全双工 | 双方随时收发 | 聊天、游戏 | push_client.py |
4. asyncio.gather() 并发执行
# 同时运行多个异步任务
await asyncio.gather(
task1(), # 任务 1
task2(), # 任务 2
task3() # 任务 3
)
# 所有任务并发执行,等待全部完成
应用:
- 服务端:同时推送消息 + 接收消息
- 客户端:同时接收推送 + 等待用户输入
实际应用场景
1. 即时聊天应用
# 服务端广播消息给所有客户端
connected_clients = set()
async def broadcast(message):
await asyncio.gather(
*[client.send(message) for client in connected_clients]
)
2. 股票行情推送
# 服务端定时推送最新价格
async def push_stock_price(websocket):
while True:
price = get_current_price()
await websocket.send(json.dumps({"price": price}))
await asyncio.sleep(1)
3. 在线游戏同步
# 玩家动作同步给其他玩家
async def sync_player_action(action):
for player in other_players:
await player.websocket.send(action)
4. 协作编辑
# 用户编辑时,推送给其他用户
async def on_document_change(change):
for user in connected_users:
await user.websocket.send(change)
HTTP vs WebSocket
| 对比项 | HTTP | WebSocket |
|---|---|---|
| 连接方式 | 短连接(请求-响应) | 长连接(持久化) |
| 通信方向 | 单向(客户端 → 服务端) | 双向(双方互发) |
| 服务端推送 | ❌ 不支持(需轮询) | ✅ 支持 |
| 实时性 | 低(轮询延迟) | 高(实时推送) |
| 带宽占用 | 高(重复请求头) | 低(保持连接) |
| 适用场景 | RESTful API、网页浏览 | 聊天、游戏、实时数据 |
HTTP 轮询示例(低效)
# 客户端每秒轮询一次
while True:
response = requests.get("http://server/messages")
if response.json()["has_new"]:
print("有新消息")
time.sleep(1) # 浪费带宽和资源
WebSocket 推送(高效)
# 有消息时服务端主动推送
async for message in websocket:
print("收到新消息:", message)
常见问题
Q1: 为什么要用 async/await?
A: WebSocket 需要同时处理多个连接和消息,异步编程可以高效利用资源。
# 同步代码:一次只能处理一个客户端
def handle_client(websocket):
for message in websocket: # 阻塞在这里
send_reply(message)
# 异步代码:可以同时处理多个客户端
async def handle_client(websocket):
async for message in websocket: # 不阻塞其他客户端
await send_reply(message)
Q2: 客户端必须等待服务端回复吗?
A: 不是必须的,这取决于你的业务需求。
- 需要等待回复:查询类操作(发送请求,等待结果)
- 不需要等待:发送日志、打点数据(发完就不管)
- 同时收发:聊天、游戏(全双工通信)
Q3: WebSocket 安全吗?
A: WebSocket 支持加密连接(wss://),就像 HTTPS。
# 未加密
uri = "ws://localhost:8765"
# 加密(需要 SSL 证书)
uri = "wss://example.com:8765"
Q4: 如何处理断线重连?
async def connect_with_retry():
while True:
try:
async with websockets.connect(uri) as websocket:
await handle_messages(websocket)
except Exception as e:
print("连接断开,3 秒后重连...")
await asyncio.sleep(3)
快速参考
启动顺序
# 1. 先启动服务端
python simple_server.py # 或 push_server.py
# 2. 再启动客户端
python simple_client.py # 或 push_client.py
修改推送间隔
# push_server.py 第 31 行
await asyncio.sleep(3) # 改成 1 就是 1 秒推送一次
修改端口
# 服务端
websockets.serve(handle_client, "localhost", 8765) # 改端口
# 客户端
uri = "ws://localhost:8765" # 改成对应端口
总结
通过这两个示例,你已经掌握了:
✅ WebSocket 的基本原理
✅ 客户端发送、服务端响应(simple)
✅ 服务端主动推送(push)
✅ 全双工通信(同时收发)
✅ 异步编程的核心概念
下一步学习方向
- 多客户端广播:一个客户端发消息,所有客户端都收到
- JSON 数据传输:结构化数据通信
- 房间/频道机制:聊天室、游戏房间
- 身份认证:Token 验证
- 断线重连:自动重连机制

浙公网安备 33010602011771号