跳转到内容
搜索文档

使用 WebSocket

最后更新 查看 MarkdownAgent 设置

Durable Objects 可作为 WebSocket 服务器,每个实例连接数千客户端。您也可以将 WebSocket 用作客户端以连接其他服务器或 Durable Objects。

有两种 WebSocket API 可用:

  1. 休眠 WebSocket API - 允许 Durable Object 在不活跃时休眠而不断开客户端连接。(推荐)
  2. Web 标准 WebSocket API - 使用熟悉的 addEventListener 事件模式。

什么是 WebSocket?

WebSocket 是长连接 TCP 连接,支持客户端与服务器之间的双向实时通信。

主要特性:

  • Workers 和 Durable Objects 均可作为 WebSocket 端点(客户端或服务器)
  • WebSocket 会话是长连接的,使 Durable Objects 非常适合接受连接
  • 单个 Durable Object 实例可协调多个客户端(例如聊天室或多人游戏)

请参阅 Cloudflare Edge Chat Demo 了解将 Durable Objects 与 WebSocket 一起使用的示例。

为什么使用休眠?

休眠 WebSocket API 通过允许 Durable Objects 在不活跃时休眠来降低成本:

  • 客户端保持连接,而 Durable Object 不在内存中
  • 休眠期间不产生可计费 Duration (GB-s) 费用
  • 消息到达时,Durable Object 自动唤醒

Durable Objects 休眠 WebSocket API

休眠 WebSocket API 扩展了 Web 标准 WebSocket API,以在不活跃期间降低成本。

休眠工作原理

当 Durable Object 在短时间内没有收到任何事件(如警报或消息)时,它将从内存中逐出。在休眠期间:

  • WebSocket 客户端保持与 Cloudflare 网络的连接
  • 内存中状态被重置
  • 当事件到达时,Durable Object 被重新初始化,并且其 constructor 运行

要在休眠后恢复状态,请使用 serializeAttachment and deserializeAttachment 将数据持久化到每个 WebSocket 连接中。

有关更多信息,请参阅 Durable Object 生命周期

休眠示例

要在 Durable Objects 中使用 WebSocket:

  1. 将请求从 Worker 代理到 Durable Object
  2. 调用 DurableObjectState::acceptWebSocket 来接受服务器端连接
  3. 在 Durable Object 类上为相关事件定义处理程序方法

如果已休眠的 Durable Object 发生事件,运行时会通过调用构造函数重新初始化它。使用休眠时,请尽量减少构造函数中的工作量。

import { DurableObject } from "cloudflare:workers";

// Durable Object
export class WebSocketHibernationServer extends DurableObject {
	async fetch(request) {
		// Creates two ends of a WebSocket connection.
		const webSocketPair = new WebSocketPair();
		const [client, server] = Object.values(webSocketPair);

		// Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
		// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
		// When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
		this.ctx.acceptWebSocket(server);

		return new Response(null, {
			status: 101,
			webSocket: client,
		});
	}

	async webSocketMessage(ws, message) {
		// Upon receiving a message from the client, reply with the same message,
		// but will prefix the message with "[Durable Object]: " and return the number of connections.
		ws.send(
			`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
		);
	}

	async webSocketClose(ws, code, reason, wasClean) {
		// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
		// auto-replies to Close frames. Calling close() is safe but no longer required.
		ws.close(code, reason);
	}
}
import { DurableObject } from "cloudflare:workers";

export interface Env {
	WEBSOCKET_HIBERNATION_SERVER: DurableObjectNamespace<WebSocketHibernationServer>;
}

// Durable Object
export class WebSocketHibernationServer extends DurableObject {
	async fetch(request: Request): Promise<Response> {
		// Creates two ends of a WebSocket connection.
		const webSocketPair = new WebSocketPair();
		const [client, server] = Object.values(webSocketPair);

		// Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
		// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
		// When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
		this.ctx.acceptWebSocket(server);

		return new Response(null, {
			status: 101,
			webSocket: client,
		});
	}

	async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
		// Upon receiving a message from the client, reply with the same message,
		// but will prefix the message with "[Durable Object]: " and return the number of connections.
		ws.send(
			`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
		);
	}

	async webSocketClose(
		ws: WebSocket,
		code: number,
		reason: string,
		wasClean: boolean,
	) {
		// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
		// auto-replies to Close frames. Calling close() is safe but no longer required.
		ws.close(code, reason);
	}
}
from workers import Response, DurableObject
from js import WebSocketPair

# Durable Object

class WebSocketHibernationServer(DurableObject):
def **init**(self, state, env):
super().**init**(state, env)
self.ctx = state

    async def fetch(self, request):
        # Creates two ends of a WebSocket connection.
        client, server = WebSocketPair.new().object_values()

        # Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
        # Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
        # When the Durable Object receives a message during Hibernation, it will run the `__init__` to be re-initialized
        self.ctx.acceptWebSocket(server)

        return Response(
            None,
            status=101,
            web_socket=client
        )

    async def webSocketMessage(self, ws, message):
        # Upon receiving a message from the client, reply with the same message,
        # but will prefix the message with "[Durable Object]: " and return the number of connections.
        ws.send(
            f"[Durable Object] message: {message}, connections: {len(self.ctx.get_websockets())}"
        )

    async def webSocketClose(self, ws, code, reason, was_clean):
        # With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
        # auto-replies to Close frames. Calling close() is safe but no longer required.
        ws.close(code, reason)

在 Wrangler 文件中配置 Durable Object 绑定迁移

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "websocket-hibernation-server",
	"durable_objects": {
		"bindings": [
			{
				"name": "WEBSOCKET_HIBERNATION_SERVER",
				"class_name": "WebSocketHibernationServer"
			}
		]
	},
	"migrations": [
		{
			"tag": "v1",
			"new_sqlite_classes": ["WebSocketHibernationServer"]
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "websocket-hibernation-server"

[[durable_objects.bindings]]
name = "WEBSOCKET_HIBERNATION_SERVER"
class_name = "WebSocketHibernationServer"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "WebSocketHibernationServer" ]

完整示例见 使用 WebSocket Hibernation 构建 WebSocket 服务器

自动 Ping/Pong 处理

Cloudflare 运行时会自动处理 WebSocket 协议的 ping 帧:

  • 传入的 ping 帧 会收到自动 pong 响应
  • Ping/pong 处理不会中断休眠
  • 不会为控制帧调用 webSocketMessage 处理程序

此行为使连接保持活动状态,而无需唤醒 Durable Object。

批量发送消息以减少开销

由于 JavaScript 运行时与底层系统之间的上下文切换,每条 WebSocket 消息都会产生处理开销。发送许多小消息可能会使单个 Durable Object 过载。即使总数据量很小也会发生这种情况。

要最大化吞吐量:

  • 将多个逻辑消息合并为单个 WebSocket 帧
  • 使用简单的信封格式来打包和拆包批量消息
  • 目标是发送更少、更大的消息,而不是许多小消息
import { DurableObject } from "cloudflare:workers";

// Define a batch envelope format

// Client-side: batch messages before sending
function sendBatch(ws, messages) {
	const batch = {
		messages,
		timestamp: Date.now(),
	};
	ws.send(JSON.stringify(batch));
}

// Durable Object: process batched messages
export class GameRoom extends DurableObject {
	async webSocketMessage(ws, message) {
		if (typeof message !== "string") return;

		const batch = JSON.parse(message);

		// Process all messages in the batch in a single handler invocation
		for (const msg of batch.messages) {
			this.handleMessage(ws, msg);
		}
	}

	handleMessage(ws, msg) {
		// Handle individual message logic
	}
}
import { DurableObject } from "cloudflare:workers";

// Define a batch envelope format
interface BatchedMessage {
	messages: Array<{ type: string; payload: unknown }>;
	timestamp: number;
}

// Client-side: batch messages before sending
function sendBatch(
	ws: WebSocket,
	messages: Array<{ type: string; payload: unknown }>,
) {
	const batch: BatchedMessage = {
		messages,
		timestamp: Date.now(),
	};
	ws.send(JSON.stringify(batch));
}

// Durable Object: process batched messages
export class GameRoom extends DurableObject<Env> {
	async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
		if (typeof message !== "string") return;

		const batch = JSON.parse(message) as BatchedMessage;

		// Process all messages in the batch in a single handler invocation
		for (const msg of batch.messages) {
			this.handleMessage(ws, msg);
		}
	}

	private handleMessage(
		ws: WebSocket,
		msg: { type: string; payload: unknown },
	) {
		// Handle individual message logic
	}
}

为什么批量发送有帮助

WebSocket 读取需要在内核和 JavaScript 运行时之间进行上下文切换。每条单独的消息都会触发此开销。将 10-100 个逻辑消息批量发送到单个 WebSocket 帧中可以成比例地减少上下文切换。

对于传感器读数或游戏状态更新等高频数据,请使用基于时间或基于数量的批量发送。每 50-100 毫秒或每 50-100 条消息进行批量发送,以先到者为准。

扩展方法

以下方法在休眠 WebSocket API 上可用。在休眠之前和之后使用它们来持久化和恢复状态。

WebSocket.serializeAttachment

  • serializeAttachment(value any) : void

保留与 WebSocket 连接关联的 value 副本。

关键行为:

  • 只要 WebSocket 保持健康,序列化附件就会在休眠期间持久存在
  • 如果任一方关闭连接,附件将会丢失
  • 调用此方法后对 value 的修改不会被保留,除非您再次调用该方法
  • value 可以是结构化克隆算法支持的任何类型
  • 最大序列化大小为 16,384 字节

对于较大的值或必须在 WebSocket 生命周期之外持久存在的数据,请使用 Storage API 并将相应的键存储为附件。

WebSocket.deserializeAttachment

  • deserializeAttachment(): any

检索传递给 serializeAttachment() 的最新值,如果不存在则返回 null

附件示例

使用 serializeAttachmentdeserializeAttachment 来跨休眠持久化每个连接的状态:

import { DurableObject } from "cloudflare:workers";

export class WebSocketServer extends DurableObject {
	async fetch(request) {
		const url = new URL(request.url);
		const orderId = url.searchParams.get("orderId") ?? "anonymous";

		const webSocketPair = new WebSocketPair();
		const [client, server] = Object.values(webSocketPair);

		this.ctx.acceptWebSocket(server);

		// Persist per-connection state that survives hibernation
		const state = {
			orderId,
			joinedAt: Date.now(),
		};
		server.serializeAttachment(state);

		return new Response(null, { status: 101, webSocket: client });
	}

	async webSocketMessage(ws, message) {
		// Restore state after potential hibernation
		const state = ws.deserializeAttachment();
		ws.send(`Hello ${state.orderId}, you joined at ${state.joinedAt}`);
	}

	async webSocketClose(ws, code, reason, wasClean) {
		const state = ws.deserializeAttachment();
		console.log(`${state.orderId} disconnected`);
		// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
		// auto-replies to Close frames. Calling close() is safe but no longer required.
		ws.close(code, reason);
	}
}
import { DurableObject } from "cloudflare:workers";

interface ConnectionState {
orderId: string;
joinedAt: number;
}

export class WebSocketServer extends DurableObject<Env> {
	async fetch(request: Request): Promise<Response> {
		const url = new URL(request.url);
		const orderId = url.searchParams.get("orderId") ?? "anonymous";

    	const webSocketPair = new WebSocketPair();
    	const [client, server] = Object.values(webSocketPair);

    	this.ctx.acceptWebSocket(server);

    	// Persist per-connection state that survives hibernation
    	const state: ConnectionState = {
    		orderId,
    		joinedAt: Date.now(),
    	};
    	server.serializeAttachment(state);

    	return new Response(null, { status: 101, webSocket: client });
    }

    async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
    	// Restore state after potential hibernation
    	const state = ws.deserializeAttachment() as ConnectionState;
    	ws.send(`Hello ${state.orderId}, you joined at ${state.joinedAt}`);
    }

    async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) {
    	const state = ws.deserializeAttachment() as ConnectionState;
    	console.log(`${state.orderId} disconnected`);
    	// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
    	// auto-replies to Close frames. Calling close() is safe but no longer required.
    	ws.close(code, reason);
    }

}

WebSocket 标准 API

WebSocket 连接是通过发送带有 Upgrade: websocket 标头的 HTTP GET 请求来建立的。

典型流程:

  1. Worker 验证升级请求
  2. Worker 将请求代理到 Durable Object
  3. Durable Object 接受服务器端连接
  4. Worker 在响应中返回客户端连接
// Worker
export default {
	async fetch(request, env, ctx) {
		if (request.method === "GET" && request.url.endsWith("/websocket")) {
			// Expect to receive a WebSocket Upgrade request.
			// If there is one, accept the request and return a WebSocket Response.
			const upgradeHeader = request.headers.get("Upgrade");
			if (!upgradeHeader || upgradeHeader !== "websocket") {
				return new Response(null, {
					status: 426,
					statusText: "Durable Object expected Upgrade: websocket",
					headers: {
						"Content-Type": "text/plain",
					},
				});
			}

			// This example will refer to a single Durable Object instance, since the name "foo" is
			// hardcoded
			let stub = env.WEBSOCKET_SERVER.getByName("foo");

			// The Durable Object's fetch handler will accept the server side connection and return
			// the client
			return stub.fetch(request);
		}

		return new Response(null, {
			status: 400,
			statusText: "Bad Request",
			headers: {
				"Content-Type": "text/plain",
			},
		});
	},
};
// Worker
export default {
	async fetch(request, env, ctx): Promise<Response> {
		if (request.method === "GET" && request.url.endsWith("/websocket")) {
			// Expect to receive a WebSocket Upgrade request.
			// If there is one, accept the request and return a WebSocket Response.
			const upgradeHeader = request.headers.get("Upgrade");
			if (!upgradeHeader || upgradeHeader !== "websocket") {
				return new Response(null, {
					status: 426,
					statusText: "Durable Object expected Upgrade: websocket",
					headers: {
						"Content-Type": "text/plain",
					},
				});
			}

			// This example will refer to a single Durable Object instance, since the name "foo" is
			// hardcoded
			let stub = env.WEBSOCKET_SERVER.getByName("foo");

			// The Durable Object's fetch handler will accept the server side connection and return
			// the client
			return stub.fetch(request);
		}

		return new Response(null, {
			status: 400,
			statusText: "Bad Request",
			headers: {
				"Content-Type": "text/plain",
			},
		});
	},
} satisfies ExportedHandler<Env>;
from workers import Response, WorkerEntrypoint

# Worker

class Default(WorkerEntrypoint):
async def fetch(self, request):
if request.method == "GET" and request.url.endswith("/websocket"): # Expect to receive a WebSocket Upgrade request. # If there is one, accept the request and return a WebSocket Response.
upgrade_header = request.headers.get("Upgrade")
if not upgrade_header or upgrade_header != "websocket":
return Response(
None,
status=426,
status_text="Durable Object expected Upgrade: websocket",
headers={
"Content-Type": "text/plain",
},
)

            # This example will refer to a single Durable Object instance, since the name "foo" is
            # hardcoded
            stub = self.env.WEBSOCKET_SERVER.getByName("foo")

            # The Durable Object's fetch handler will accept the server side connection and return
            # the client
            return await stub.fetch(request)

        return Response(
            None,
            status=400,
            status_text="Bad Request",
            headers={
                "Content-Type": "text/plain",
            },
        )

以下 Durable Object 创建 WebSocket 连接,并用总连接数响应消息:

import { DurableObject } from "cloudflare:workers";

// Durable Object
export class WebSocketServer extends DurableObject {
	currentlyConnectedWebSockets;

	constructor(ctx, env) {
		super(ctx, env);
		this.currentlyConnectedWebSockets = 0;
	}

	async fetch(request) {
		// Creates two ends of a WebSocket connection.
		const webSocketPair = new WebSocketPair();
		const [client, server] = Object.values(webSocketPair);

		// Calling `accept()` connects the WebSocket to this Durable Object
		server.accept();
		this.currentlyConnectedWebSockets += 1;

		// Upon receiving a message from the client, the server replies with the same message,
		// and the total number of connections with the "[Durable Object]: " prefix
		server.addEventListener("message", (event) => {
			server.send(
				`[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`,
			);
		});

		// When the client closes the connection, clean up the server side.
		// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
		// auto-replies to Close frames. Calling close() is safe but no longer required.
		server.addEventListener("close", (cls) => {
			this.currentlyConnectedWebSockets -= 1;
			server.close(cls.code, "Durable Object is closing WebSocket");
		});

		return new Response(null, {
			status: 101,
			webSocket: client,
		});
	}
}
// Durable Object
export class WebSocketServer extends DurableObject {
	currentlyConnectedWebSockets: number;

	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
		this.currentlyConnectedWebSockets = 0;
	}

	async fetch(request: Request): Promise<Response> {
		// Creates two ends of a WebSocket connection.
		const webSocketPair = new WebSocketPair();
		const [client, server] = Object.values(webSocketPair);

		// Calling `accept()` connects the WebSocket to this Durable Object
		server.accept();
		this.currentlyConnectedWebSockets += 1;

		// Upon receiving a message from the client, the server replies with the same message,
		// and the total number of connections with the "[Durable Object]: " prefix
		server.addEventListener("message", (event: MessageEvent) => {
			server.send(
				`[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`,
			);
		});

		// When the client closes the connection, clean up the server side.
		// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
		// auto-replies to Close frames. Calling close() is safe but no longer required.
		server.addEventListener("close", (cls: CloseEvent) => {
			this.currentlyConnectedWebSockets -= 1;
			server.close(cls.code, "Durable Object is closing WebSocket");
		});

		return new Response(null, {
			status: 101,
			webSocket: client,
		});
	}
}
from workers import Response, DurableObject
from js import WebSocketPair
from pyodide.ffi import create_proxy

# Durable Object

class WebSocketServer(DurableObject):
def **init**(self, ctx, env):
super().**init**(ctx, env)
self.currently_connected_websockets = 0

    async def fetch(self, request):
        # Creates two ends of a WebSocket connection.
        client, server = WebSocketPair.new().object_values()

        # Calling `accept()` connects the WebSocket to this Durable Object
        server.accept()
        self.currently_connected_websockets += 1

        # Upon receiving a message from the client, the server replies with the same message,
        # and the total number of connections with the "[Durable Object]: " prefix
        def on_message(event):
            server.send(
                f"[Durable Object] currentlyConnectedWebSockets: {self.currently_connected_websockets}"
            )

        server.addEventListener("message", create_proxy(on_message))

        # When the client closes the connection, clean up the server side.
        # With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
        # auto-replies to Close frames. Calling close() is safe but no longer required.
        def on_close(event):
            self.currently_connected_websockets -= 1
            server.close(event.code, "Durable Object is closing WebSocket")

        server.addEventListener("close", create_proxy(on_close))

        return Response(
            None,
            status=101,
            web_socket=client,
        )

使用 Durable Object 绑定迁移 来配置您的 Wrangler 文件:

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "websocket-server",
	"durable_objects": {
		"bindings": [
			{
				"name": "WEBSOCKET_SERVER",
				"class_name": "WebSocketServer"
			}
		]
	},
	"migrations": [
		{
			"tag": "v1",
			"new_sqlite_classes": ["WebSocketServer"]
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "websocket-server"

[[durable_objects.bindings]]
name = "WEBSOCKET_SERVER"
class_name = "WebSocketServer"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "WebSocketServer" ]

完整的示例可以在 构建 WebSocket 服务器 中找到。

相关资源

这篇文档对您有帮助吗?