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 -------------------->| 关闭连接

关键点

  1. 必须先启动服务端,客户端才能连接
  2. 一问一答模式:客户端发送 → 等待回复 → 再发送
  3. 这是最基础的 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 ----------------------->| 关闭连接

关键点

  1. 服务端主动推送:每 3 秒自动发送,不需要客户端请求
  2. 全双工通信:推送和对话同时进行,互不干扰
  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)
✅ 全双工通信(同时收发)
✅ 异步编程的核心概念

下一步学习方向

  1. 多客户端广播:一个客户端发消息,所有客户端都收到
  2. JSON 数据传输:结构化数据通信
  3. 房间/频道机制:聊天室、游戏房间
  4. 身份认证:Token 验证
  5. 断线重连:自动重连机制

posted @ 2026-07-01 11:07  乐乐乐乐乐乐樂  阅读(158)  评论(0)    收藏  举报
jQuery火箭图标返回顶部代码