跳到主要内容

使用 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://
  • 消息大小:避免发送过大的消息,可能导致传输失败或性能问题

相关文档