Skip to content

WebSocket

Node.js 云函数支持 WebSocket 长连接,可用于实时通知和协作等场景。

创建 WebSocket 端点

创建一个以 .node.js 结尾的文件,例如 ws.node.js。这个文件既是云函数文件,也是 WebSocket 的连接路径:

text
wss://example.com/ws.node.js

WebSocket 使用网站本身的域名、标准端口和文件路径,不需要额外监听端口,也没有必须使用的固定路径。网址中需要保留 .node.js 后缀。

云函数后端代码

WebSocket 连接中会提供全局的 websocket 对象。

js
if (websocket) {
	websocket.on("open", () => {
		websocket.send("连接成功");
	});

	websocket.on("message", (data) => {
		websocket.send(`收到消息:${data}`);
	});

	websocket.on("close", (code, reason) => {
		console.log(`连接已关闭:${code} ${reason}`);
	});

	websocket.on("error", (error) => {
		console.log(error.message);
	});
} else {
	res.status(426);
	document.write("请使用 WebSocket 连接");
}

同一个 .node.js 文件仍可通过普通 HTTP 请求访问。普通 HTTP 请求中,websocket 的值为 null,可以像上面的示例一样分别处理两种请求。

网页前端代码

浏览器使用标准的 WebSocket 接口连接:

js
const socket = new WebSocket("/ws.node.js");

socket.addEventListener("message", (event) => {
	console.log(event.data);
});

socket.addEventListener("open", () => {
	socket.send("Hello");
});

跨连接发送

使用 websocket.sendToAll() 可以向同一域名、同一 WebSocket 端点中的所有已连接客户端发送消息,包含当前连接。例如,在新连接建立时更新所有客户端显示的在线人数:

js
if (websocket) {
	websocket.on("open", async () => {
		const peerIds = await websocket.getPeers();
		const notification = JSON.stringify({
			onlineCount: peerIds.length + 1,
			type: "presence",
		});
		websocket.sendToAll(notification);
	});
}

如果只需要向个别客户端发送,可以使用 websocket.getPeers() 获取其他已连接客户端的 ID,并通过 websocket.sendTo() 向指定客户端发送。例如,在新连接建立时只与第一个已在线的客户端配对,并通知双方:

js
if (websocket) {
	websocket.on("open", async () => {
		const peerIds = await websocket.getPeers();
		const partnerId = peerIds[0];
		if (!partnerId) {
			websocket.send(JSON.stringify({ type: "waiting" }));
			return;
		}
		websocket.send(
			JSON.stringify({ partnerId: partnerId, type: "matched" }),
		);
		websocket.sendTo(
			partnerId,
			JSON.stringify({ partnerId: websocket.id, type: "matched" }),
		);
	});
}

getPeers() 会在调用时查询当前开启的其他连接,不包含当前连接、正在关闭或已经关闭的连接。如果连接在发送时关闭,sendTo()sendToAll() 会忽略该连接。

完整示例

下面的示例会显示当前页面的在线连接数,并在连接建立或断开时实时更新所有客户端。

js
if (websocket) {
	websocket.on("open", async () => {
		const peerIds = await websocket.getPeers();
		const notification = JSON.stringify({
			onlineCount: peerIds.length + 1,
			type: "presence",
		});
		websocket.sendToAll(notification);
	});

	websocket.on("close", async () => {
		const peerIds = await websocket.getPeers();
		const notification = JSON.stringify({
			onlineCount: peerIds.length,
			type: "presence",
		});
		websocket.sendToAll(notification);
	});

	websocket.on("error", (error) => {
		console.error(error.message);
	});
} else {
	res.status(426);
	document.write("请使用 WebSocket 连接");
}
html
<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>实时在线状态</title>
	</head>
	<body>
		<h1>实时在线状态</h1>
		<p id="status">正在连接</p>
		<p>当前在线:<strong id="online-count">0</strong></p>

		<script>
			const socket = new WebSocket("/presence.node.js");

			socket.addEventListener("open", () => {
				document.getElementById("status").textContent = "已连接";
			});

			socket.addEventListener("message", (event) => {
				const data = JSON.parse(event.data);
				document.getElementById("online-count").textContent = data.onlineCount;
			});

			socket.addEventListener("close", () => {
				document.getElementById("status").textContent = "连接已断开";
				document.getElementById("online-count").textContent = "0";
			});
		</script>
	</body>
</html>

接口

事件

  • websocket.on("open", listener):连接建立后触发。
  • websocket.on("message", (data) => {}):收到文本消息时触发,data 是字符串。
  • websocket.on("close", (code, reason) => {}):连接关闭后触发。
  • websocket.on("error", (error) => {}):连接发生错误时触发。

事件监听函数中的 this 指向当前 websocket 对象。监听函数可以是异步函数,返回的 Promise 会在本次事件结束前被等待。如需在事件之间共享和保留状态,请使用内置的数据库

方法

  • websocket.getPeers():异步返回同一域名、同一 WebSocket 端点中其他已打开连接的 ID 数组,需要使用 await
  • websocket.send(data):向当前连接发送字符串。
  • websocket.sendTo(peerId, data):向指定的已打开连接发送字符串;连接不存在或已经关闭时不发送。
  • websocket.sendToAll(data):向同一域名、同一 WebSocket 端点中所有已打开的连接发送字符串,包含当前连接。
  • websocket.close(code?, reason?):关闭当前连接。状态码和原因均可省略。

属性和状态

  • websocket.id:当前连接的唯一标识。
  • websocket.protocol:协商后的子协议;未使用子协议时为空字符串。
  • websocket.readyState:当前连接状态。
  • websocket.CONNECTINGwebsocket.OPENwebsocket.CLOSINGwebsocket.CLOSED:可与 readyState 比较的状态常量。