使用 WebSocket
本文介绍如何在 HTTP 云函数中实现 WebSocket 长连接,支持服务端与客户端之间的双向实时通信。
什么是 WebSocket
「WebSocket」是一种在单个 TCP 连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。与传统的 HTTP 请求-响应模式不同,WebSocket 建立连接后可以保持长连接,实现服务端和客户端之间的实时双向数据传输。
主要特点:
- 全双工通信:服务端和客户端可以同时发送和接收消息
- 持久连接:建立连接后保持长连接,无需重复握手
- 低延迟:消息传递实时高效,适合实时应用场景
- 协议切换:通过 HTTP Upgrade 机制从 HTTP 协议升级为 WebSocket 协议
典型应用场景:
- 实时聊天应用
- 在线协作编辑
- 实时数据监控
- 游戏服务器
- 实时通知推送
基本使用
1. 实现 WebSocket 云函数
在云函数中使用 WebSocket 需要实现两个关键部分:
1.1 实现 handleUpgrade 方法
handleUpgrade 方法用于处理 WebSocket 协议升级请求,返回是否允许建立 WebSocket 连接。
exports.main = async function (event, context) {
// 处理 WebSocket 消息的主逻辑
if (context.ws) {
// WebSocket 连接已建立
context.ws.on("message", (msg) => {
console.log("收到消息: ", msg);
context.ws.send(`服务端回复: ${msg}`);
});
}
};
// 处理 WebSocket 升级请求
exports.main.handleUpgrade = async function (context) {
return {
allowWebSocket: true, // 允许建立 WebSocket 连接
};
};
1.2 实现消息处理逻辑
在主函数中通过 context.ws 处理 WebSocket 连接的各种事件。
2. 完整示例
exports.main = async function (event, context) {
const { ws } = context;
if (ws) {
// 连接建立事件
ws.on("open", (msg) => {
console.log("WebSocket 连接已建立", msg);
ws.send("欢迎连接到 WebSocket 服务");
});
// 接收消息事件
ws.on("message", (msg) => {
console.log("收到客户端消息: ", msg);
// 回复客户端
ws.send(`服务端收到消息: ${msg}`);
// 发送 JSON 数据
ws.send(JSON.stringify({
type: "response",
data: msg,
timestamp: Date.now()
}));
});
// 连接关闭事件
ws.on("close", (msg) => {
console.log("WebSocket 连接已关闭", msg);
});
// 错误事件
ws.on("error", (error) => {
console.error("WebSocket 错误: ", error);
});
}
};
// 处理 WebSocket 协议升级
exports.main.handleUpgrade = async function (context) {
// 可以在这里进行身份验证、权限检查等
const { httpContext } = context;
const { headers } = httpContext;
// 示例:验证 token
const token = headers.authorization;
if (!token) {
return {
allowWebSocket: false, // 拒绝连接
statusCode: 401,
headers: {
"Content-Type": "text/plain"
},
body: "未授权"
};
}
// 允许连接
return {
allowWebSocket: true
};
};
WebSocket API
context.ws 对象
在 WebSocket 连接建立后,context.ws 对象提供以下方法和事件:
事件监听
| 事件名 | 说明 | 回调参数 |
|---|---|---|
open | 连接建立时触发 | message: 连接信息 |
message | 接收到消息时触发 | message: 接收到的消息内容 |
close | 连接关闭时触发 | message: 关闭信息 |
error | 发生错误时触发 | error: 错误对象 |
发送消息
// 发送文本消息
ws.send("Hello, client!");
// 发送 JSON 数据
ws.send(JSON.stringify({ type: "notification", content: "更新通知" }));
handleUpgrade 返回值
handleUpgrade 方法返回一个对象,用于控制 WebSocket 连接的建立:
interface UpgradeResponse {
allowWebSocket: boolean; // 是否允许建立 WebSocket 连接
statusCode?: number; // HTTP 状态码(拒绝连接时)
headers?: Record<string, string | string[]>; // HTTP 响应头(拒绝连接时)
body?: string; // HTTP 响应体(拒绝连接时)
}
高级用法
1. 身份验证
在 handleUpgrade 中验证客户端身份:
exports.main.handleUpgrade = async function (context) {
const { httpContext, extendedContext } = context;
// 验证用户身份
const userId = extendedContext?.userId;
if (!userId) {
return {
allowWebSocket: false,
statusCode: 401,
body: "需要登录"
};
}
// 验证权限
const hasPermission = await checkUserPermission(userId);
if (!hasPermission) {
return {
allowWebSocket: false,
statusCode: 403,
body: "无权限访问"
};
}
return { allowWebSocket: true };
};
2. 广播消息
将消息发送给所有连接的客户端:
// 存储所有连接的客户端
const clients = new Set();
exports.main = async function (event, context) {
const { ws } = context;
if (ws) {
// 添加到客户端列表
clients.add(ws);
ws.on("message", (msg) => {
// 广播给所有客户端
clients.forEach(client => {
if (client !== ws) { // 不发送给自己
client.send(msg);
}
});
});
ws.on("close", () => {
// 移除断开的客户端
clients.delete(ws);
});
}
};
3. 心跳检测
实现心跳机制保持连接活跃:
exports.main = async function (event, context) {
const { ws } = context;
if (ws) {
let heartbeatInterval;
ws.on("open", () => {
// 每 30 秒发送心跳
heartbeatInterval = setInterval(() => {
ws.send(JSON.stringify({ type: "ping" }));
}, 30000);
});
ws.on("message", (msg) => {
try {
const data = JSON.parse(msg);
// 响应心跳
if (data.type === "pong") {
console.log("收到心跳响应");
}
} catch (e) {
// 处理非 JSON 消息
}
});
ws.on("close", () => {
// 清除心跳定时器
clearInterval(heartbeatInterval);
});
}
};
4. 房间管理
实现简单的房间功能:
// 房间管理
const rooms = new Map();
exports.main = async function (event, context) {
const { ws } = context;
if (ws) {
let currentRoom = null;
ws.on("message", (msg) => {
try {
const data = JSON.parse(msg);
// 加入房间
if (data.action === "join") {
currentRoom = data.room;
if (!rooms.has(currentRoom)) {
rooms.set(currentRoom, new Set());
}
rooms.get(currentRoom).add(ws);
ws.send(JSON.stringify({
type: "system",
message: `已加入房间: ${currentRoom}`
}));
}
// 发送消息到房间
if (data.action === "message" && currentRoom) {
const roomClients = rooms.get(currentRoom);
roomClients.forEach(client => {
client.send(JSON.stringify({
type: "message",
room: currentRoom,
content: data.content
}));
});
}
} catch (e) {
console.error("消息处理错误:", e);
}
});
ws.on("close", () => {
// 离开房间
if (currentRoom && rooms.has(currentRoom)) {
rooms.get(currentRoom).delete(ws);
// 清理空房间
if (rooms.get(currentRoom).size === 0) {
rooms.delete(currentRoom);
}
}
});
}
};
客户端连接示例
浏览器 JavaScript
// 连接 WebSocket
const ws = new WebSocket('wss://your-service.run.tcloudbase.com/ws');
// 连接建立
ws.onopen = () => {
console.log('WebSocket 连接已建立');
ws.send('Hello, server!');
};
// 接收消息
ws.onmessage = (event) => {
console.log('收到消息:', event.data);
try {
const data = JSON.parse(event.data);
console.log('解析的数据:', data);
} catch (e) {
// 处理非 JSON 消息
}
};
// 连接关闭
ws.onclose = () => {
console.log('WebSocket 连接已关闭');
};
// 错误处理
ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
};
// 发送消息
function sendMessage(msg) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(msg);
}
}
Node.js 客户端
const WebSocket = require('ws');
const ws = new WebSocket('wss://your-service.run.tcloudbase.com/ws');
ws.on('open', () => {
console.log('连接已建立');
ws.send('Hello, server!');
});
ws.on('message', (data) => {
console.log('收到消息:', data.toString());
});
ws.on('close', () => {
console.log('连接已关闭');
});
ws.on('error', (error) => {
console.error('错误:', error);
});
使用 TypeScript
使用 TypeScript 编写 WebSocket 云函数:
import { TcbEventFunction } from "@cloudbase/functions-typings";
// 主函数
export const main: TcbEventFunction = async function (event, context) {
const { ws } = context;
if (ws) {
ws.on("open", (msg) => {
console.log("WebSocket 连接已建立:", msg);
});
ws.on("message", (msg) => {
console.log("收到消息:", msg);
ws.send(`回复: ${msg}`);
});
ws.on("close", (msg) => {
console.log("连接关闭:", msg);
});
ws.on("error", (error) => {
console.error("错误:", error);
});
}
};
// 处理升级请求
main.handleUpgrade = async function (context) {
return {
allowWebSocket: true,
};
};
注意事项
⚠️ 注意:以下事项在使用 WebSocket 时需要特别注意
- 连接保持:WebSocket 连接会长期占用资源,需要合理控制连接数量和实现连接超时机制
- 错误处理:务必监听
error事件,避免未捕获的异常导致云函数异常退出 - 消息格式:建议使用 JSON 格式传输结构化数据,便于解析和扩展
- 安全验证:在
handleUpgrade中实现身份验证和权限检查,避免未授权访问 - 资源清理:连接关闭时及时清理相关资源(定时器、监听器等)
- 协议升级:客户端连接时需要使用
ws://或wss://协议(生产环境建议使用wss://) - 消息大小:避免发送过大的消息,可能导致传输失败或性能问题