包参考
@overworld-engine/relay
net 的参考 WebSocket 中继服务器:房间广播、心跳剔除、优雅关闭
@overworld-engine/net 的 createWebSocketTransport 需要一个服务端,把每条消息
原样广播给同一房间内其他所有连接。本包就是那个契约的生产可用参考实现:
可用 npx 直接跑,也可编程嵌入既有 HTTP 服务。它是纯中继——不解析信封
({ from, data } 对服务器完全不透明)、不做权威仲裁,只负责按房间转发、心跳
剔除死连接与优雅关闭。线路协议的完整规范见 net 的「线路协议规范」。
安装 / 命令行
最快路径是直接 npx,无需写任何代码:
npx @overworld-engine/relay # overworld-relay,默认监听 8787
PORT=9000 npx @overworld-engine/relay
HEARTBEAT_MS=10000 npx @overworld-engine/relayCtrl+C(SIGINT/SIGTERM)会给所有客户端发 close 1001 后退出。作为依赖安装则:
pnpm add @overworld-engine/relay编程 API
import { createRelayServer } from '@overworld-engine/relay'
const relay = createRelayServer({
port: 8787, // 0 = 随机端口,ready 之后读 relay.port
heartbeatMs: 30_000, // ping 间隔;错过整周期没有 pong 即 terminate;0 关闭心跳
maxPayloadBytes: 64 * 1024, // 超限的连接以 1009 关闭
onJoin: (room, n) => console.log(room, n),
onLeave: (room, n) => console.log(room, n),
logger: console.log, // 省略或 false = 静默
})
await relay.ready // 开始监听(端口被占用时 reject)
relay.port // 实际端口
relay.rooms() // Map<房间路径, 人数> 快照
await relay.close() // 全员 close 1001,拒绝新连接,幂等挂到既有 http server
import { createServer } from 'http'
const server = createServer(app) // 你的 HTTP 服务
const relay = createRelayServer({ server, path: '/ws' })
server.listen(8080)
// ws://host:8080/ws/lobby → 房间 '/lobby';/ws 本身 → 房间 '/'
// 前缀之外的 WebSocket 升级会被 close 1008;relay.close() 不会关掉你的 server房间语义
房间 = 连接时的 URL 路径:ws://host:8787/room-a 只与同路径的客户端互转,
省略路径即默认房间 /。没有 join/leave 帧——连接即加入、断开即离开,
一台服务器天然承载多个房间 / 多局游戏。这与 net 的线路协议一致。
与 createWebSocketTransport 对接
import { createPresenceSync, createWebSocketTransport } from '@overworld-engine/net'
const transport = createWebSocketTransport({
url: 'ws://localhost:8787/lobby', // 路径即房间
})
const sync = createPresenceSync({
transport,
getLocal: () => ({ position: getPlayerPosition() }),
})
sync.start()边界
只做转发。移动校验、防作弊、状态权威等权威逻辑属于你自己的游戏服务器 (见 net 的输入预测与服务器对账与指南「权威多人」); relay 刻意不碰这些,以便任何语言都能按同一线路协议实现兼容的中继。
分层约定
基础设施层的独立发布包,配合 @overworld-engine/net 使用。它是一个 Node 服务
(依赖 ws),不进浏览器包;游戏客户端只依赖 net,不依赖 relay。