Durable Objects 可作为 WebSocket 服务器,每个实例连接数千客户端。您也可以将 WebSocket 用作客户端以连接其他服务器或 Durable Objects。
有两种 WebSocket API 可用:
- 休眠 WebSocket API - 允许 Durable Object 在不活跃时休眠而不断开客户端连接。(推荐)
- Web 标准 WebSocket API - 使用熟悉的
addEventListener事件模式。
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 自动唤醒
休眠 WebSocket API 扩展了 Web 标准 WebSocket API,以在不活跃期间降低成本。
当 Durable Object 在短时间内没有收到任何事件(如警报或消息)时,它将从内存中逐出。在休眠期间:
- WebSocket 客户端保持与 Cloudflare 网络的连接
- 内存中状态被重置
- 当事件到达时,Durable Object 被重新初始化,并且其
constructor运行
要在休眠后恢复状态,请使用 serializeAttachment and deserializeAttachment 将数据持久化到每个 WebSocket 连接中。
有关更多信息,请参阅 Durable Object 生命周期。
要在 Durable Objects 中使用 WebSocket:
- 将请求从 Worker 代理到 Durable Object
- 调用
DurableObjectState::acceptWebSocket来接受服务器端连接 - 在 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 服务器。
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 上可用。在休眠之前和之后使用它们来持久化和恢复状态。
serializeAttachment(value:any)void
保留与 WebSocket 连接关联的 value 副本。
关键行为:
- 只要 WebSocket 保持健康,序列化附件就会在休眠期间持久存在
- 如果任一方关闭连接,附件将会丢失
- 调用此方法后对
value的修改不会被保留,除非您再次调用该方法 value可以是结构化克隆算法 ↗支持的任何类型- 最大序列化大小为 16,384 字节
对于较大的值或必须在 WebSocket 生命周期之外持久存在的数据,请使用 Storage API 并将相应的键存储为附件。
deserializeAttachment():any
检索传递给 serializeAttachment() 的最新值,如果不存在则返回 null。
使用 serializeAttachment 和 deserializeAttachment 来跨休眠持久化每个连接的状态:
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 连接是通过发送带有 Upgrade: websocket 标头的 HTTP GET 请求来建立的。
典型流程:
- Worker 验证升级请求
- Worker 将请求代理到 Durable Object
- Durable Object 接受服务器端连接
- 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 服务器 中找到。
- Mozilla 开发者网络 (MDN) 关于 WebSocket 类的文档 ↗
- Cloudflare 的 WebSocket 模板,用于在使用 WebSocket 的 Workers 上构建应用程序 ↗
- Durable Object 基类
- Durable Object State 接口