第6课:WebSocket 实时通信 —— 前后端怎么"打电话"
本课目标
搞懂浏览器和服务器之间是怎么实时传递消息的。这是聊天界面、实时邮件更新、看板刷新的基础。
HTTP vs WebSocket
你可能知道 HTTP——浏览器向服务器发请求,服务器回应。但 HTTP 有个问题:服务器不能主动给浏览器发消息。
HTTP 模式(传统):
浏览器: "有新邮件吗?"
服务器: "没有"
(1秒后)
浏览器: "有新邮件吗?"
服务器: "没有"
(1秒后)
浏览器: "有新邮件吗?"
服务器: "有!给你"
← 这叫"轮询",浪费资源且有延迟
WebSocket 模式(本项目):
浏览器: "建立 WebSocket 连接"
服务器: "OK,通道已开"
(什么都不做,安静等待)
服务器: "嘿!新邮件来了,给你数据" ← 服务器主动推送!
浏览器: "收到"
服务器: "AI 回复来了,给你" ← 又一条推送
WebSocket = 一根持续保持的双向通道。建立连接后,双方都能随时发消息。
在邮件助手里,WebSocket 传什么?
服务器 → 浏览器(推送)
| 消息类型 | 什么时候发 | 携带什么数据 |
|---|---|---|
inbox_update |
邮件列表变化 | 最新邮件列表 |
assistant_message |
AI 生成回复 | 文字内容 |
tool_use |
AI 在调用工具 | 工具名和参数 |
tool_result |
工具执行完成 | 执行结果 |
result |
对话结束 | 会话 ID |
listener_notification |
监听器被触发 | 触发详情 |
ui_state_update |
看板数据变化 | 新的数据 |
component_instance |
需要渲染组件 | 组件类型和数据 |
listeners_update |
监听器列表变化 | 活跃监听器列表 |
浏览器 → 服务器(请求)
| 消息类型 | 什么时候发 | 携带什么数据 |
|---|---|---|
chat |
用户发送聊天消息 | 文字内容 |
subscribe |
页面加载时 | 会话 ID |
request_inbox |
打开收件箱标签 | 无 |
component_action |
用户点击看板按钮 | 操作类型和参数 |
消息流实例:聊天过程
当你在聊天框输入"帮我找上周的发票",数据是这样流动的:
sequenceDiagram
participant B as 浏览器
participant S as 服务器
participant AI as Claude Agent SDK
B->>S: { type: "chat", content: "帮我找上周的发票" }
S->>AI: 传给 AI 处理
AI-->>S: tool_use: email_search
S-->>B: { type: "tool_use", name: "email_search" }<br>告诉前端: AI 正在搜索邮件
Note over S: 工具执行搜索
S-->>B: { type: "tool_result", result: [...] }<br>搜索结果返回
AI-->>S: 整理答案
S-->>B: { type: "assistant_message", content: "我找到了3封发票邮件..." }
S-->>B: { type: "result", session_id: "abc123" }<br>这轮对话结束
前端根据不同的消息类型,实时更新界面——显示"AI 正在搜索..."、显示搜索结果、显示最终回复。
前端怎么处理 WebSocket?
位置:client/hooks/useWebSocket.ts
// 简化版
function useWebSocket() {
const [messages, setMessages] = useState([]);
const ws = useRef(null);
useEffect(() => {
// 建立连接
ws.current = new WebSocket('ws://localhost:3000/ws');
// 收到消息时
ws.current.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'assistant_message':
// 把 AI 回复加到聊天列表
setMessages(prev => [...prev, {
role: 'assistant',
content: data.content
}]);
break;
case 'inbox_update':
// 刷新收件箱
refreshInbox(data.emails);
break;
case 'ui_state_update':
// 更新看板数据
updateState(data.stateId, data.data);
break;
case 'listener_notification':
// 显示通知
showNotification(data.message);
break;
}
};
}, []);
// 发送消息的方法
const sendMessage = (content) => {
ws.current.send(JSON.stringify({
type: 'chat',
content: content
}));
};
return { messages, sendMessage };
}
后端怎么管理 WebSocket?
位置:ccsdk/websocket-handler.ts
后端需要处理多个客户端同时连接的情况:
// 简化版
class WebSocketHandler {
private clients = new Set(); // 所有连接的客户端
handleConnection(ws) {
this.clients.add(ws);
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.type === 'chat') {
// 用户发了聊天消息 → 传给 AI
this.handleChat(msg.content, ws);
}
});
ws.on('close', () => {
this.clients.delete(ws);
});
}
// 广播:给所有连接的客户端发送消息
broadcast(message) {
for (const client of this.clients) {
client.send(JSON.stringify(message));
}
}
}
broadcast 很重要——当 Listener 在后台自动处理了一封邮件(比如贴了标签),它通过 broadcast 通知所有打开着浏览器的人,实时刷新界面。
为什么不全用 HTTP?
| 场景 | 用 HTTP | 用 WebSocket |
|---|---|---|
| 加载邮件列表 | ✅ 合适 | 没必要 |
| AI 聊天(流式回复) | ❌ 等太久 | ✅ 实时推送每个字 |
| 新邮件到达通知 | ❌ 得不停轮询 | ✅ 服务器主动推 |
| 看板数据实时更新 | ❌ 手动刷新 | ✅ 自动更新 |
这个 demo 的策略是:初始加载用 HTTP,后续更新用 WebSocket。
本课小结
- WebSocket 是双向实时通道,服务器能主动给浏览器推消息
- 9种推送消息 + 4种请求消息 覆盖了所有场景
- 聊天过程是多步骤流式推送:工具调用 → 结果 → AI回复
- broadcast 让所有客户端实时同步
- HTTP 负责初始加载,WebSocket 负责后续更新