WebSocket 实时通信应用
本文引用的文件列表 - examples/ws-min/app.mts - examples/ws-min/ws-min.toml - examples/public/websocket_echo_app.js - src/executor/inproc_vjsx_websocket_affinity_runtime.v - src/executor/inproc_vjsx_websocket_affinity_state.v - src/executor/inproc_vjsx_websocket_policy.v - src/executor/inproc_vjsx_websocket_event_runtime.v - src/executor/inproc_vjsx_websocket_response_codec.v - src/websocket_ingress_runtime.v - src/ws/hub_runtime.v - examples/paseo-relay/app.mts - articles/08-vjsx-intro.md
目录
简介
本指南面向使用 VJSX(vhttpd 的 JS/TS 运行时)构建 WebSocket 实时通信应用的开发者,系统讲解基于 VJSX 的 WebSocket 服务器实现、连接管理、消息广播与房间系统;并给出客户端 JavaScript SDK 的使用建议、连接重连机制与错误处理策略。文档同时覆盖会话亲和性、负载均衡与高可用部署策略,并提供最小化 WebSocket 应用示例以及聊天、数据同步、协作编辑等典型场景的实现思路。
项目结构
仓库中与 WebSocket 和 VJSX 相关的关键位置如下: - 最小示例:examples/ws-min(VJSX 应用 + 站点配置) - 浏览器端演示:examples/public/websocket_echo_app.js - VJSX 执行器与亲和性:src/executor/websocket - 入站升级与响应编码:src/websocket_ingress_runtime.v, src/executor/inproc_vjsx_websocket_response_codec.v - 房间与广播:src/ws/hub_runtime.v - 进阶示例:examples/paseo-relay/app.mts(展示 websocket_affinity 回调) - 文档参考:articles/08-vjsx-intro.md(VJSX 入口与事件说明)
graph TB
Client["浏览器/客户端"] --> Ingress["WebSocket 入站<br/>websocket_ingress_runtime.v"]
Ingress --> Executor["VJSX 执行器<br/>inproc_vjsx_*_websocket*.v"]
Executor --> App["VJSX 应用逻辑<br/>examples/ws-min/app.mts"]
Executor --> Hub["房间与广播中心<br/>ws/hub_runtime.v"]
App --> |命令: join/send/broadcast| Hub
Hub --> |下发命令| Executor
Executor --> Ingress
Ingress --> Client
图表来源 - src/websocket_ingress_runtime.v - src/executor/inproc_vjsx_websocket_event_runtime.v - src/executor/inproc_vjsx_websocket_response_codec.v - src/ws/hub_runtime.v - examples/ws-min/app.mts
章节来源 - examples/ws-min/app.mts - examples/ws-min/ws-min.toml - examples/public/websocket_echo_app.js - src/websocket_ingress_runtime.v - src/executor/inproc_vjsx_websocket_response_codec.v - src/ws/hub_runtime.v
核心组件
- VJSX WebSocket 应用接口
- 通过导出默认对象暴露 websocket(frame) 处理器,接收帧事件 open/message/close,返回 accepted/closed 及 commands 列表。
- 支持 set_meta/join/leave/send/send_to/broadcast/broadcast_dispatch/close 等命令控制连接与会话状态。
- 执行器与亲和性
- 根据配置或应用回调计算 affinity_key,将同一会话的请求路由到固定 lane(线程),保证状态一致性。
- 支持从 query/header/app 三种来源解析 key,并可设置优先级与是否钉住 lane。
- 房间与广播
- hub 提供 join/leave/send/broadcast 等命令的执行与跨 worker 分发能力,支持按房间广播与排除特定连接。
- 入站与响应编解码
- 入站负责 HTTP 升级到 WebSocket 并调用 VJSX 处理器;响应编码器将 JS 返回值规范化为内部命令。
章节来源 - examples/ws-min/app.mts - src/executor/inproc_vjsx_websocket_affinity_runtime.v - src/executor/inproc_vjsx_websocket_policy.v - src/executor/inproc_vjsx_websocket_response_codec.v - src/ws/hub_runtime.v
架构总览
下图展示了从浏览器连接到 VJSX 应用再到房间广播的端到端流程。
sequenceDiagram
participant C as "客户端"
participant I as "入站升级<br/>websocket_ingress_runtime.v"
participant E as "VJSX 执行器<br/>inproc_vjsx_*_websocket*.v"
participant A as "VJSX 应用<br/>app.mts"
participant H as "房间中心<br/>hub_runtime.v"
C->>I : "HTTP /ws 升级请求"
I->>E : "构造 frame(event=open)"
E->>A : "调用 websocket(frame)"
A-->>E : "{accepted : true, commands : [...]}"
E->>H : "join/set_meta/broadcast 等命令"
H-->>E : "命令执行结果/失败"
E-->>I : "标准化响应"
I-->>C : "建立 WebSocket 连接/发送首条消息"
C->>I : "WS 消息"
I->>E : "frame(event=message)"
E->>A : "再次调用 websocket(frame)"
A-->>E : "commands : send/broadcast..."
E->>H : "执行命令"
H-->>E : "完成"
E-->>I : "写入 WS 帧"
I-->>C : "推送消息"
图表来源 - src/websocket_ingress_runtime.v - src/executor/inproc_vjsx_websocket_event_runtime.v - src/executor/inproc_vjsx_websocket_response_codec.v - src/ws/hub_runtime.v - examples/ws-min/app.mts
详细组件分析
VJSX WebSocket 应用开发
- 入口与事件
- 在默认导出对象中实现 websocket(frame),处理 open/message/close 三类事件。
- 通过 frame.query/frame.headers/frame.metadata 获取上下文信息。
- 常用命令
- set_meta/clear_meta:设置/清除连接元数据
- join/leave:加入/离开房间
- send/send_to:向指定连接发送消息
- broadcast/broadcast_dispatch:按房间广播
- close:关闭连接(可带 code/reason)
- 二进制与文本
- 支持 text/binary 两种 opcode,并在命令中指定。
章节来源 - examples/ws-min/app.mts - articles/08-vjsx-intro.md
类图:VJSX WebSocket 应用与命令
classDiagram
class VJSXApp {
+websocket(frame) Response
}
class Frame {
+string id
+string event
+string path
+map~string,string~ query
+map~string,string~ headers
+string dataText()
+any dataJson(default)
+string opcode
+map~string,string~ metadata
}
class Response {
+bool accepted
+bool closed
+Command[] commands
+string errorClass
}
class Command {
+string event
+string id
+string targetId
+string room
+string data
+string opcode
+int code
+string reason
}
VJSXApp --> Frame : "接收"
VJSXApp --> Response : "返回"
Response --> Command : "包含"
图表来源 - examples/ws-min/app.mts - src/executor/inproc_vjsx_websocket_response_codec.v
连接管理与房间系统
- 房间模型
- 每个连接维护 rooms 集合与 metadata 键值对。
- 通过 join/leave 动态变更房间成员关系。
- 广播机制
- hub 根据房间名查找目标连接,构建 info 帧并派发至对应 worker,再执行 send/broadcast 等命令。
- 支持排除特定连接(except_id)。
flowchart TD
Start(["收到消息"]) --> Parse["解析消息类型"]
Parse --> Type{"类型?"}
Type --> |ping| ReplyPong["返回 pong"]
Type --> |echo| Echo["封装 echo 响应"]
Type --> |其他| Ignore["忽略或记录"]
ReplyPong --> End(["结束"])
Echo --> End
Ignore --> End
图表来源 - examples/ws-min/app.mts - src/ws/hub_runtime.v
章节来源 - src/ws/hub_runtime.v
会话亲和性与 Lane 绑定
- 亲和性来源
- 可从 query/header/app 三种来源解析 affinity_key。
- 支持 priority 与 should_pin_lane 决策。
- 绑定与迁移
- 首次计算后缓存 connection->lane 映射;支持迁移旧 key 到新 key。
- 当 key 为空且 fallback=reject 时拒绝连接。
flowchart TD
Enter(["进入亲和性计算"]) --> CheckExisting["检查已缓存 affinity_key"]
CheckExisting --> HasKey{"存在?"}
HasKey --> |是| UseExisting["使用已有 key"]
HasKey --> |否| Source{"source 类型"}
Source --> |app| CallApp["调用 app.websocket_affinity(frame)"]
Source --> |query/header| Extract["从 query/header 提取 key"]
CallApp --> Decide["合并 key 与 priority"]
Extract --> Decide
Decide --> Empty{"key 是否为空?"}
Empty --> |是| Fallback{"fallback=reject?"}
Fallback --> |是| Reject["拒绝连接"]
Fallback --> |否| NoPin["不钉住 lane"]
Empty --> |否| PinCheck{"should_pin_lane?"}
PinCheck --> |是| AcquireLane["按 key 获取/创建 lane 并绑定"]
PinCheck --> |否| NextLane["随机分配 lane 并记录 key"]
AcquireLane --> Done(["完成"])
NextLane --> Done
Reject --> Done
图表来源 - src/executor/inproc_vjsx_websocket_affinity_runtime.v - src/executor/inproc_vjsx_websocket_affinity_state.v - src/executor/inproc_vjsx_websocket_policy.v - examples/paseo-relay/app.mts
章节来源 - src/executor/inproc_vjsx_websocket_affinity_runtime.v - src/executor/inproc_vjsx_websocket_affinity_state.v - src/executor/inproc_vjsx_websocket_policy.v - examples/paseo-relay/app.mts
入站升级与错误处理
- 升级流程
- 入站将 HTTP 升级为 WebSocket,并将 open 事件交由 VJSX 执行器处理。
- 若处理器返回 error 或未接受,则返回 4xx/5xx 并附带原因。
- 错误分类
- 支持 error_class 用于区分业务错误与运行时错误,便于前端重试与降级。
sequenceDiagram
participant C as "客户端"
participant I as "入站"
participant E as "执行器"
participant A as "应用"
C->>I : "HTTP /ws 升级"
I->>E : "dispatch(open)"
E->>A : "websocket(frame)"
A-->>E : "{error/errorClass}"
alt 发生错误
E-->>I : "event='error'"
I-->>C : "返回 500/4xx 并携带原因"
else 正常
E-->>I : "accepted=true, commands=[...]"
I-->>C : "升级成功/下发初始消息"
end
图表来源 - src/websocket_ingress_runtime.v - src/executor/inproc_vjsx_websocket_response_codec.v
章节来源 - src/websocket_ingress_runtime.v - src/executor/inproc_vjsx_websocket_response_codec.v
客户端 JavaScript SDK 与重连机制
- 基础用法
- 使用原生 WebSocket API 即可与 vhttpd 服务交互;示例见 examples/public/websocket_echo_app.js。
- 重连策略建议
- 指数退避:初次失败等待 t,后续每次翻倍,上限 T_max。
- 抖动:在退避基础上增加随机抖动,避免雪崩。
- 心跳保活:周期性 ping/pong,超时判定断线并重连。
- 幂等恢复:服务端在 open 阶段下发必要状态,客户端据此恢复本地视图。
- 错误处理
- 区分网络错误与服务端拒绝(如 403/401),前者重试,后者提示用户或跳转登录。
- 利用 error_class 做差异化处理(例如认证失败、限流、系统错误)。
章节来源 - examples/public/websocket_echo_app.js
依赖关系分析
- 模块耦合
- 入站层仅关注协议升级与错误映射,业务逻辑下沉到 VJSX 应用。
- 执行器负责亲和性、任务队列与命令编解码,与 hub 解耦。
- hub 专注房间与广播,屏蔽底层多 worker 细节。
- 外部依赖
- 无额外第三方库依赖,全部由 vhttpd 内核与 VJSX 运行时提供。
graph LR
Ingress["websocket_ingress_runtime.v"] --> Exec["inproc_vjsx_*_websocket*.v"]
Exec --> Codec["inproc_vjsx_websocket_response_codec.v"]
Exec --> Affinity["inproc_vjsx_websocket_affinity_*.v"]
Exec --> Policy["inproc_vjsx_websocket_policy.v"]
Exec --> Hub["ws/hub_runtime.v"]
App["examples/ws-min/app.mts"] --> Exec
图表来源 - src/websocket_ingress_runtime.v - src/executor/inproc_vjsx_websocket_response_codec.v - src/executor/inproc_vjsx_websocket_affinity_runtime.v - src/executor/inproc_vjsx_websocket_affinity_state.v - src/executor/inproc_vjsx_websocket_policy.v - src/ws/hub_runtime.v - examples/ws-min/app.mts
性能与扩展性
- 单节点内广播
- hub 在同一进程内跨 worker 分发,适合中小规模并发。
- 多节点扩展
- 可在 hub 之上接入外部消息总线(如 Redis/NATS)实现跨节点广播。
- 亲和性与负载
- 合理设置 affinity_key 与 priority,减少跨 lane 切换带来的序列化开销。
- 对于无状态广播型场景,可不钉住 lane,提升均衡度。
- 资源控制
- 限制每连接房间数、广播频率与消息大小,防止热点房间放大效应。
[本节为通用指导,无需源码引用]
故障排查指南
- 常见问题定位
- 连接被拒:检查入站返回码与 error_class,确认鉴权与参数校验。
- 无法加入房间:确认 join 命令是否被执行,rooms 快照是否正确。
- 广播未到达:核对房间名一致性与 except_id 排除逻辑。
- 亲和性异常:检查 affinity_key 生成逻辑与 fallback 策略。
- 日志与追踪
- 利用 request_id/trace_id 串联请求链路,快速定位问题。
- 在关键路径打印命令执行结果与失败原因。
章节来源 - src/websocket_ingress_runtime.v - src/ws/hub_runtime.v
结论
vhttpd 的 VJSX 运行时提供了完整的 WebSocket 开发体验:简洁的应用接口、强大的房间与广播能力、灵活的会话亲和性与完善的错误处理。结合合理的客户端重连策略与部署模式,可高效构建聊天、数据同步与协作编辑等实时应用。
[本节为总结,无需源码引用]
附录:最小实现与典型场景
最小化 WebSocket 应用
- 服务端
- 在 VJSX 应用中实现 websocket(frame),open 时设置元数据、加入房间并下发初始消息;message 时回显或转发。
- 站点配置
- 启用 websocket_dispatch,指向 VJSX 应用入口。
- 客户端
- 使用原生 WebSocket 进行连接、发送与接收。
章节来源 - examples/ws-min/app.mts - examples/ws-min/ws-min.toml - examples/public/websocket_echo_app.js
典型场景实现要点
- 实时聊天
- 使用房间隔离频道;join 后广播新成员加入;消息经 hub 广播给房间内除发送者外的所有连接。
- 数据同步
- 在 open 阶段下发全量状态;后续增量更新以 diff 形式推送;客户端合并冲突采用最后写入优先或操作转换。
- 协作编辑
- 引入 CRDT/OT 算法;将用户操作序列化为消息;通过房间广播;服务端维护版本向量确保一致性。
[本节为概念性说明,无需源码引用]