跳到主要内容

WebSocket 协议支持

本文介绍如何在 Web 云函数中实现 WebSocket 长连接,支持服务端与客户端之间的双向实时通信。

什么是 WebSocket

「WebSocket」是一种在单个 TCP 连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。与传统的 HTTP 请求-响应模式不同,WebSocket 建立连接后可以保持长连接,实现服务端和客户端之间的实时双向数据传输。

主要特点

  • 全双工通信:服务端和客户端可以同时发送和接收消息
  • 持久连接:建立连接后保持长连接,无需重复握手
  • 低延迟:消息传递实时高效,适合实时应用场景
  • 协议切换:通过 HTTP Upgrade 机制从 HTTP 协议升级为 WebSocket 协议

典型应用场景

  • 实时聊天应用
  • 在线协作编辑
  • 实时数据监控
  • 游戏服务器
  • 实时通知推送

工作原理

服务启动

在支持 WebSocket 的 Web 云函数中,通过启动文件启动 WebSocket 服务器,服务器必须在 9000 端口 上监听。

建立连接

  1. 客户端通过 ws://wss:// 协议向云函数发起连接请求
  2. 云函数平台将连接透传给运行环境中的 WebSocket 服务进程(9000 端口)
  3. 连接协商及后续通信完全由您的 WebSocket 服务端代码处理

连接生命周期

  • 连接与实例映射:一次 WebSocket 连接的生命周期等同于一次函数调用请求
    • 连接建立 = 请求发起
    • 连接断开 = 请求结束
  • 实例映射:函数实例与连接是一一对应的,同一实例在某一时刻仅处理一个 WebSocket 连接,新连接会启动新的实例
  • 连接保持:连接建立后,实例持续运行,处理双向数据传输
  • 连接结束:当 WebSocket 连接断开时,对应的函数实例停止运行

使用限制

在使用 WebSocket 时,需要注意以下配额限制:

限制项说明
空闲超时时间10 ~ 7200 秒,连接上无消息传输时的超时时间
执行超时时间必须 ≥ 空闲超时时间,函数的最大运行时长
单次消息大小最大 256KB
单连接传输速率最大 128KB/s
单连接请求 QPS最大 10 次/秒

连接断开与状态码

不同的断开情况对应不同的函数状态码:

断开情况函数表现函数状态码
正常关闭
状态码为 1000、1010、1011
函数正常执行结束200
异常关闭
非标准关闭状态码
函数异常结束439 (服务端关闭)
456 (客户端关闭)
空闲超时
连接无消息传输超过配置时间
函数异常结束455
执行超时
连接持续时间超过函数最大运行时长
函数异常结束433

操作步骤

步骤1:开启 WebSocket 协议支持

在创建或编辑云函数时,需要在控制台开启 WebSocket 协议支持。

  1. 登录 云开发平台/云函数/函数列表
  2. 点击「新建云函数」或选择已有函数点击「编辑」
  3. 在「函数配置」中找到「WebSocket 协议」配置项

显示 WebSocket 协议开关和空闲超时时间配置

  1. 开启「WebSocket 协议」开关
  2. 设置「WebSocket 空闲超时时间」(范围:10-7200 秒)
  3. 确保「执行超时时间」≥「空闲超时时间」
  4. 点击「保存」完成配置

⚠️ 注意:WebSocket 协议一旦开启后不可取消,但空闲超时时间可以后续修改。

步骤2:安装依赖并编写服务端代码

根据您使用的编程语言,安装对应的 WebSocket 库并编写服务端代码。WebSocket 服务器必须监听 9000 端口

安装依赖

安装 ws 库:

npm install ws

package.json 中添加依赖:

{
"dependencies": {
"ws": "^8.0.0"
}
}

编写服务端代码

使用 ws 库实现 WebSocket 服务器:

const WebSocket = require('ws');

// 创建 WebSocket 服务器,必须监听 9000 端口
const wss = new WebSocket.Server({ port: 9000 });

wss.on('connection', (ws) => {
console.log('新连接建立');

// 发送欢迎消息
ws.send('欢迎连接到 WebSocket 服务');

// 接收消息
ws.on('message', (message) => {
console.log('收到消息:', message.toString());

// 回复消息
const response = {
type: 'response',
data: message.toString(),
timestamp: Date.now(),
};
ws.send(JSON.stringify(response));
});

// 连接关闭
ws.on('close', () => {
console.log('连接已关闭');
});

// 错误处理
ws.on('error', (error) => {
console.error('WebSocket 错误:', error);
});
});

console.log('WebSocket 服务器已启动,监听端口 9000');

步骤3:部署云函数

完成代码编写后,部署云函数:

  • 在控制台点击「部署」按钮
  • 或使用 CloudBase CLI:tcb fn deploy --httpFn

步骤4:客户端连接测试

使用浏览器或 Node.js 客户端连接 WebSocket 服务:

// 浏览器端
const ws = new WebSocket('wss://your-function.run.tcloudbase.com');

ws.onopen = () => {
console.log('连接已建立');
ws.send('Hello, server!');
};

ws.onmessage = (event) => {
console.log('收到消息:', event.data);
};

ws.onclose = () => {
console.log('连接已关闭');
};

ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
};

高级用法

1. 广播消息

将消息发送给所有连接的客户端:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 9000 });

// 存储所有连接的客户端
const clients = new Set();

wss.on('connection', (ws) => {
// 添加到客户端列表
clients.add(ws);
console.log(`新连接建立,当前连接数: ${clients.size}`);

ws.on('message', (message) => {
const msg = message.toString();
console.log('收到消息:', msg);

// 广播给所有客户端
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(
JSON.stringify({
type: 'broadcast',
data: msg,
timestamp: Date.now(),
})
);
}
});
});

ws.on('close', () => {
// 移除断开的客户端
clients.delete(ws);
console.log(`连接已关闭,当前连接数: ${clients.size}`);
});
});

2. 心跳检测

实现心跳机制保持连接活跃:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 9000 });

wss.on('connection', (ws) => {
let isAlive = true;

// 心跳检测:每 30 秒发送 ping
const heartbeatInterval = setInterval(() => {
if (!isAlive) {
console.log('心跳超时,关闭连接');
ws.terminate();
return;
}

isAlive = false;
ws.ping();
}, 30000);

// 收到 pong 响应
ws.on('pong', () => {
isAlive = true;
});

ws.on('message', (message) => {
console.log('收到消息:', message.toString());
ws.send(`回复: ${message.toString()}`);
});

ws.on('close', () => {
console.log('连接已关闭');
clearInterval(heartbeatInterval);
});
});

3. 房间管理

实现简单的房间功能,支持多个客户端加入不同房间进行分组通信:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 9000 });

// 房间管理
const rooms = new Map();

wss.on('connection', (ws) => {
let currentRoom = null;

ws.on('message', (message) => {
try {
const data = JSON.parse(message.toString());

// 加入房间
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}`,
roomSize: rooms.get(currentRoom).size,
})
);
}

// 发送消息到房间
if (data.action === 'message' && currentRoom) {
const roomClients = rooms.get(currentRoom);
roomClients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(
JSON.stringify({
type: 'message',
room: currentRoom,
content: data.content,
timestamp: Date.now(),
})
);
}
});
}
} 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);
}
}
});
});

客户端连接示例

// 连接 WebSocket
const ws = new WebSocket('wss://your-service.run.tcloudbase.com');

// 连接建立
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);
}
}

常见问题

1. 连接无法建立怎么办?

  • 检查是否在控制台开启了 WebSocket 协议支持
  • 确认 WebSocket 服务器正在监听 9000 端口
  • 确认使用正确的 WebSocket 地址(ws://wss://
  • 检查云函数是否正常部署和运行
  • 查看云函数日志是否有错误信息

2. 连接频繁断开怎么办?

  • 检查空闲超时时间配置是否合理(建议设置较大值,如 3600 秒)
  • 实现心跳机制保持连接活跃(每 30 秒发送一次 ping/pong)
  • 检查客户端网络是否稳定
  • 查看云函数日志,确认是否有异常错误导致连接断开
  • 确保执行超时时间 ≥ 空闲超时时间

3. 如何实现多人聊天室?

参考本文「高级用法 - 房间管理」章节,使用 MapSet 存储房间和连接信息。对于大规模应用,建议:

  • 使用 Redis 等外部存储管理连接状态和房间信息
  • 实现连接池管理
  • 考虑使用消息队列处理广播消息

4. 如何处理大量并发连接?

  • 实例扩展:云函数会自动为每个 WebSocket 连接创建独立实例
  • 状态管理:使用 Redis 等外部存储共享连接状态
  • 消息队列:使用消息队列(如 TDMQ)处理广播和异步消息
  • 负载均衡:多个云函数实例自动负载均衡
  • 资源监控:关注函数实例数量和资源使用情况

5. 9000 端口被占用怎么办?

云函数运行环境中,9000 端口是专门为 WebSocket 保留的,不会被其他服务占用。如果本地开发时遇到端口占用:

# 查找占用 9000 端口的进程
lsof -i :9000

# 终止进程(Mac/Linux)
kill -9 <PID>

6. 如何实现断线重连?

客户端实现自动重连机制:

class ReconnectingWebSocket {
constructor(url, maxRetries = 5) {
this.url = url;
this.maxRetries = maxRetries;
this.retries = 0;
this.connect();
}

connect() {
this.ws = new WebSocket(this.url);

this.ws.onopen = () => {
console.log('连接已建立');
this.retries = 0; // 重置重试计数
};

this.ws.onclose = () => {
console.log('连接已关闭');
this.reconnect();
};

this.ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
};

this.ws.onmessage = (event) => {
console.log('收到消息:', event.data);
};
}

reconnect() {
if (this.retries < this.maxRetries) {
this.retries++;
const delay = Math.min(1000 * Math.pow(2, this.retries), 30000);
console.log(`${delay}ms 后尝试重连 (${this.retries}/${this.maxRetries})`);

setTimeout(() => {
this.connect();
}, delay);
} else {
console.error('达到最大重试次数,停止重连');
}
}

send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
console.warn('连接未打开,无法发送消息');
}
}
}

// 使用
const ws = new ReconnectingWebSocket('wss://your-function.run.tcloudbase.com');

相关文档