main
simcu::websocket — 仓颉 WebSocket 库(RFC 6455)
事件回调式 WebSocket 客户端与服务端,纯仓颉实现,仅依赖标准库 + stdx(无第三方库)。
- 组织:
simcu - 包名:
websocket - 子包:
simcu::websocket.client/simcu::websocket.server/simcu::websocket.common - 构建:
cjpm build;测试:cjpm test(8 个端到端用例全通过)
功能特性
- 客户端:
connect()/send()/close()/terminate()+ 事件回调onOpen/onMessage/onError/onClose - 服务端:
on('connection')/on('message')/send()/close()/terminate()+ 监听on('close')/on('error') - RFC 6455 完整帧层:掩码、分片重组、Ping/Pong、Close 握手、
maxPayload上限(超限 1009) - 群发广播:
broadcastText/broadcastBinary - wss(TLS):
FrameStream基于StreamingSocket,可直接包裹TlsSocket(已在 BotRoleHelper 实战验证)
依赖引入
在项目 cjpm.toml 中添加:
[dependencies]
"simcu::websocket" = { path = "../websocket-cj" }
import simcu::websocket.client.WebSocketClient
import simcu::websocket.server.WebSocketServer
import simcu::websocket.server.WebSocketConnection
import simcu::websocket.common.WebSocketMessage
目录结构
src/
├── websocket.cj # 模块锚点
├── common/
│ ├── WsTypes.cj # WebSocketMessage / ReadyState / WebSocketException / CloseCodes / WsEvents
│ └── FrameStream.cj # RFC 6455 帧层(握手工具、掩码、分片重组、maxPayload)
├── client/
│ └── WebSocketClient.cj # 客户端
├── server/
│ ├── WebSocketServer.cj # 服务端监听 / 握手 / 广播
│ └── WebSocketConnection.cj # 服务端单条连接
└── tests/
└── websocket_test.cj # 端到端集成测试
客户端 API
let client = WebSocketClient("127.0.0.1", 8080, path: "/ws", maxPayload: 65536)
// 事件回调(连接建立后触发)
client.onOpen = { => ... } // 握手完成
client.onMessage = { m: WebSocketMessage => ... } // 收到消息(m.text / m.bytes / m.type)
client.onError = { e: Exception => ... } // 运行时错误(WebSocketException 含 .code)
client.onClose = { code: Int64, reason: String => ... } // 关闭,携带关闭码与原因
client.connect(timeout: Duration.second * 30) // 建立连接;握手失败抛 WebSocketException
client.send(WebSocketMessage) // 发送消息对象
client.sendText("hello") // 发送文本
client.sendBinary(byteArray) // 发送二进制
client.ping() // 心跳(服务端自动回 Pong)
client.close(code: 1000, reason: "bye") // 关闭握手,5 秒兜底强制断开
client.terminate() // 立即断开(对端收 1006)
client.readyState // Connecting / Open / Closing / Closed
client.isOpen()
构造参数:
| 参数 | 默认 | 说明 |
|---|---|---|
host: String |
— | 服务端主机(IP 或域名) |
port: UInt16 |
— | 服务端端口 |
path: String |
"/" |
请求路径 |
maxPayload: Int64 |
65536 |
单条消息最大字节数,超出触发 onError(code=1009) |
服务端 API
let server = WebSocketServer(bindAt: 8080, path: Some("/ws"), maxPayload: 65536)
// 服务端事件
server.on("connection", { conn: WebSocketConnection => ... }) // 新连接完成握手
server.on("message", { conn, msg => ... }) // 任意连接收到消息
server.on("close", { conn, code, reason => ... }) // 任意连接关闭
server.on("error", { e => ... }) // 监听 / 连接错误
server.listen() // 开始监听(异步 accept)
server.close() // 停止监听并关闭全部连接
server.broadcastText("hi-all") // 群发文本
server.broadcastBinary(bytes) // 群发二进制
server.localPort // 实际监听端口(bindAt=0 时随机)
server.connectionCount // 当前连接数
构造参数:
| 参数 | 默认 | 说明 |
|---|---|---|
bindAt: UInt16 |
0 |
监听端口,0 表示随机空闲端口(listen 后读 localPort) |
path: ?String |
None |
仅接受该路径的握手请求,None 表示不限制 |
maxPayload: Int64 |
65536 |
单条消息最大字节数 |
连接级 API
// 连接级事件(conn.on 重载按监听器参数区分,也可直接赋 conn.onMessage 等属性)
conn.on("message", { m: WebSocketMessage => ... })
conn.on("close", { code: Int64, reason: String => ... })
conn.on("error", { e: Exception => ... })
conn.send(message) / sendText(text) / sendBinary(bytes) / ping()
conn.close(code: 4000, reason: "svr") // 关闭握手
conn.terminate() // 立即断开
conn.readyState / conn.isOpen()
conn.remoteAddress // "ip:port"
底层帧 API(common 子包)
高层 client/server 已封装全部帧逻辑;需要自定义握手、代理或 wss 包裹时,可直接使用 FrameStream(lurmix 的 BotRoleHelper 即基于它实现):
import simcu::websocket.common.FrameStream
// 构造:包裹 TCP(或 TLS)流;客户端必须 maskOutgoing=true,服务端 false
let fs = FrameStream(conn, maxPayload, true)
// 握手工具
FrameStream.generateWebSocketKey() // 生成 Sec-WebSocket-Key
FrameStream.computeAccept(key) // 计算 Sec-WebSocket-Accept
FrameStream.parseClosePayload(payload) // 解析 close 帧 -> (code, reason)
// 读写
fs.readHttpHeader() // 读 HTTP 头(到 \r\n\r\n),帧字节保留在缓冲
fs.readMessage() // 读一条完整消息:分片重组、自动回 Pong;返回 ?WsFrame
fs.writeText(text) / fs.writeBinary(data)
fs.writeFrame(opcode, payload) // 发送任意 opcode 帧
fs.writePing(payload) / fs.writePong(payload)
fs.writeClose(code, reason)
fs.close() // 关闭底层流
fs.remoteAddress() // 远端 SocketAddress
WsFrame 字段:fin: Bool、opcode: Int64(1=text,2=binary,8=close)、payload: Array<Byte>。
readMessage() 返回 None 表示对端关闭/EOF;收到 close 帧时已自动回写 close 并关闭流。
wss(TLS)示例
import stdx.net.tls.*
import stdx.net.tls.common.*
let tcp = TcpSocket(host, port)
tcp.connect(timeout: Duration.second * 30)
var tls = TlsClientConfig()
tls.verifyMode = CertificateVerifyMode.TrustAll
tls.serverName = Some(host)
let tlsSocket = TlsSocket.client(tcp, session: None, clientConfig: tls)
tlsSocket.handshake(timeout: Duration.second * 30)
let fs = FrameStream(tlsSocket, maxPayload, true) // 客户端掩码 + TLS 包裹 = wss
完整实战示例见 lemon-lurmix-cj/src/helpers/BotRoleHelper.cj。
消息与错误码
// WebSocketMessage
m.type // MessageType.Text / Binary
m.bytes // 原始 payload
m.text // 文本内容(二进制消息返回 None)
WebSocketMessage.fromText("hi") / fromBinary(bytes)
// WebSocketException(异常含 RFC 6455 关闭码)
ex.code // 1002 协议错误 / 1009 消息过大 / 1006 对端异常断开 ...
CloseCodes.messageTooBig // 常用关闭码常量
// 服务端事件名(on(event, ...) 使用)
WsEvents.connection / WsEvents.message / WsEvents.close / WsEvents.error
测试覆盖
| 用例 | 验证点 |
|---|---|
| echoText | 客户端发送 → 服务端回声 → 客户端收到 |
| broadcast | 双客户端群发均收到 |
| closeHandshake | client.close(1000, "bye") 双端 onClose 收到 (1000, "bye") |
| serverClose | conn.close(4000, "svr") → 客户端收 (4000, "svr") |
| terminate | client.terminate() → 服务端收 (1006, "") |
| ping | 心跳后连接仍可收发 |
| pathFilter | path 过滤:/ws 通过,/other 拒绝 |
| maxPayload | 超限触发 onError(code=1009) |
已补齐的 API 缺口
对照最初需求(connect/send/close + onOpen/onMessage/onError/onClose),补齐了:
- sendText / sendBinary:按文本 / 二进制发送的重载(
send(WebSocketMessage)之外) - ping():心跳保活
- close(code, reason):携带关闭码与原因的关闭握手;onClose 回调带 (code, reason)
- readyState / isOpen():连接状态查询
- broadcastText / broadcastBinary:服务端群发
- localPort / connectionCount / remoteAddress:服务端与连接元信息
- 服务端 path 过滤:仅接受指定路径的握手
- maxPayload:单条消息上限,超限报 1009
尚未实现(后续方向)
- 子协议协商(Sec-WebSocket-Protocol):握手阶段携带子协议并校验
- 自动定时心跳:目前为手动
ping(),未内置保活定时器 - 分片发送:接收侧已支持分片重组;发送侧未提供手动分片 API
- 压缩扩展(permessage-deflate)
Languages
Cangjie
100%