Skip to content

WS Control Protocol

DNTOF edited this page Aug 28, 2026 · 3 revisions

WS 控制通道协议

控制接口的 WebSocket 长连接通道:一次建连、帧级开销,call/result 信封调用与 HTTP POST 完全同义的端点,另支持服务器事件流推送。传输格式为 UTF-8 JSON 文本帧(不支持二进制帧)。

前提:control_transport: ws(http/ws 二选一硬互斥)。http 模式下握手返回 404(transport_mismatch 协商信号);control_enabled: false 时一律 404;鉴权失败 403 并计入按 IP 失败锁定。

连接

ws://<服务器IP>:8081/control?key=YOUR_CONTROL_TOKEN
ws://<服务器IP>:8081/ws/control?key=YOUR_CONTROL_TOKEN    # 旧别名

也可用 X-Control-Token 请求头传递 token。

快速联调(Node.js):

const ws = new WebSocket("ws://127.0.0.1:8081/control?key=YOUR_CONTROL_TOKEN");
ws.onmessage = e => console.log("S→C:", e.data);
ws.onopen = () => ws.send(JSON.stringify({type:"call", reqId:"c1", path:"/control/command", body:{command:"help"}}));

消息类型

hello(S→C,建连后立即推送)

{"type":"hello","server":"SLDataAPI","version":"2.5.4.0","endpoints":"/control/*"}

ping / pong(C↔S 心跳)

{"type":"ping"}
{"type":"pong"}

建议每 25 秒发送一次;90 秒无任何入站消息判定空闲超时断连。协议层 WS ping 帧同样会被应答。

call(C→S)+ result(S→C)

path 必须是现有 /control/* 端点,body 为该端点的 POST body——与 HTTP POST 语义完全一致,平台可一对一映射。

{"type":"call","reqId":"c1","path":"/control/command","body":{"command":"help"}}

成功 result(data 即 HTTP 响应体):

{"type":"result","reqId":"c1","ok":true,"status":200,
 "data":{"success":true,"message":"已执行",
         "data":{"output":"Command list:\r\nquery - ...","console":"..."}}}

失败 result(业务错误:找不到玩家 / 参数缺失等):

{"type":"result","reqId":"c1","ok":false,"status":400,
 "data":{"success":false,"message":"找不到玩家: NoSuchPlayer999","data":null}}

并发 result(超出单连接 4 个并发 call 上限):

{"type":"result","reqId":"c1","ok":false,"status":429,"data":{"success":false,"message":"并发调用过多","data":null}}
  • reqId 由调用方生成,仅需连接内唯一;结果允许乱序返回(并发调用按完成顺序回包,靠 reqId 关联)
  • 协议层错误(非法 JSON / 未知消息类型):
{"type":"error","message":"未知消息类型: xxx(支持 ping / subscribe_events / call)"}

subscribe_events / unsubscribe_events(C→S 订阅控制)

{"type":"subscribe_events"}
{"type":"unsubscribe_events"}

订阅/退订确认(S→C):

{"type":"events_subscribed"}
{"type":"events_unsubscribed"}

event(S→C 事件推送)

订阅后,服务器实时推送七类事件。信封格式:

{"type":"event","event":"<事件名>","utc":"2026-08-26T16:17:05.4402075Z","data":{...}}

round_started / round_ended(回合生命周期)

{"type":"event","event":"round_started","utc":"2026-08-26T16:00:00.0000000Z",
 "data":{"started_at":"2026-08-26T16:00:00.0000000Z"}}
{"type":"event","event":"round_ended","utc":"2026-08-26T16:31:00.0000000Z",
 "data":{"leading_team":"FacilityForces","ended_at":"2026-08-26T16:31:00.0000000Z"}}

leading_teamRoundSummary.LeadingTeam 枚举名(如 FacilityForces / ChaosInsurgency / Anomalies / Dead / Draw)。

player_joined / player_left(玩家进出)

{"type":"event","event":"player_joined","utc":"2026-08-26T16:05:12.0000000Z",
 "data":{"nickname":"PlayerName","userid":"76561198000000000@steam"}}
{"type":"event","event":"player_left","utc":"2026-08-26T16:40:03.0000000Z",
 "data":{"nickname":"PlayerName","userid":"76561198000000000@steam"}}

player_died(玩家死亡,含击杀者与死亡前角色)

{"type":"event","event":"player_died","utc":"2026-08-26T16:10:45.0000000Z",
 "data":{"nickname":"PlayerName","userid":"76561198000000000@steam",
         "old_role":"ClassD",
         "attacker_nickname":"AttackerName","attacker_userid":"76561198000000001@steam"}}

无攻击者(环境/自身死亡)时攻击者字段缺省:

{"type":"event","event":"player_died","utc":"2026-08-26T16:10:50.0000000Z",
 "data":{"nickname":"PlayerName","userid":"76561198000000000@steam","old_role":"Scp049"}}

elevator_used(使用电梯)

{"type":"event","event":"elevator_used","utc":"2026-08-26T16:12:30.0000000Z",
 "data":{"nickname":"PlayerName","userid":"76561198000000000@steam","elevator_group":"Nuke01"}}

elevator_group 为当前 ElevatorGroup 枚举名(Nuke01 / Nuke02 / Scp049 / GateA01 / GateB / LczA01 / LczB / ServerRoom 等)。

door_opened(门交互,含权限门)

{"type":"event","event":"door_opened","utc":"2026-08-26T16:13:02.0000000Z",
 "data":{"nickname":"PlayerName","userid":"76561198000000000@steam",
         "door":"LCZ_ARMORY","can_open":true}}
  • doorDoorName 枚举名(LCZ_ARMORY / HCZ_049 / GATE_A 等)
  • can_open:该玩家是否有权打开(false = 有权限要求的门被无权限玩家交互,如刷卡失败)

投递语义:事件推送与 call 调用同连接复用、互不阻塞;尽力而为投递——连接断开期间的事件不补发,重连后需重新 subscribe_events

限制

参数
全局连接数 8(满载时新连接 TCP 层直接关闭,上游应退避重连)
单连接并发 call 4(超出回 result{ok:false,status:429}
单消息上限 256KB(超限按 1009 断开)
空闲超时 90s(需定期 ping)
分片组装超时 30s(慢发分片 + 夹 ping 保活无法绕过)

关闭码 / 状态码语义

阶段 含义 上游应对
HTTP 400 握手前 非升级请求 / 缺 Sec-WebSocket-Key 客户端实现缺陷,修代码而非重试
HTTP 403 握手前 鉴权失败(token 错误/锁定中) 丢弃连接 + 错误透传;不要立即重试(会计入按 IP 失败锁定,5 分钟 10 次锁 5 分钟)
HTTP 404 握手前 控制接口未启用 / 当前为 HTTP 模式(互斥) 错误透传,提示管理员改配置
HTTP 503 握手前 连接数满(上限 8) 丢弃连接 + 指数退避重连(服务端负载信号)
WS 1000 会话中 对端正常关闭 正常清理
WS 1002 会话中 协议错误(RSV 位 / 未知 opcode / 意外续帧) 客户端实现缺陷,修代码
WS 1003 会话中 收到二进制帧(仅支持文本) 同上
WS 1008 会话中 消息组装超时(分片慢速滴流防护,单消息 30s 上限) 丢弃连接 + 退避重连(可能是滥用/网络劣化信号)
WS 1009 会话中 消息超过 256KB 上限 客户端把请求拆小

另:服务端 90s 无入站消息主动断开(无 close 码层面区分)——中继侧靠心跳维持,断开后常规退避重连即可。

Clone this wiki locally