Appearance
ws — WebSocket
ws 模块提供了 WebSocket 实时通信功能,支持消息推送、房间管理、状态存储、定时器和并发锁,适用于实时游戏、聊天室、协作编辑等场景。
引入方式
javascript
var ws = require("ws")基础 API
Current — 获取当前用户 UID
javascript
var uid = ws.Current()Param — 获取请求参数
javascript
var params = ws.Param()Body — 获取请求体
javascript
var body = ws.Body()Context — 获取上下文信息
返回包含 uid、param、body 的聚合对象。
javascript
var ctx = ws.Context()消息推送
Send — 向指定用户发送消息
javascript
ws.Send("user123", {
action: "notification",
data: {
title: "新消息",
content: "您有一条新消息"
}
})| 参数 | 类型 | 说明 |
|---|---|---|
| uid | string | 目标用户 UID |
| content | map | 消息内容 |
SendAll — 广播消息
向所有在线用户广播消息。
javascript
ws.SendAll({
action: "system",
data: {
message: "系统维护通知"
}
})SendRoom — 向房间发送消息
向指定房间内的所有用户发送消息。
javascript
ws.SendRoom("room_001", {
action: "chat",
data: {
from: "张三",
message: "大家好"
}
})| 参数 | 类型 | 说明 |
|---|---|---|
| roomID | string | 房间 ID |
| content | map | 消息内容 |
房间管理
Join — 加入房间
javascript
ws.Join("room_001")Leave — 离开房间
javascript
ws.Leave("room_001")CreateRoom — 创建房间并加入
创建房间并自动将当前用户加入。maxMembers 为 0 表示不限制人数。
javascript
ws.CreateRoom("game_table_1", 4)| 参数 | 类型 | 说明 |
|---|---|---|
| roomID | string | 房间 ID |
| maxMembers | int | 最大成员数,0 表示不限 |
房间满员后,其他用户调用 ws.Join 将返回错误。
RoomInfo — 获取房间信息
返回房间成员列表、人数、最大成员数和房间状态。
javascript
var info = ws.RoomInfo("game_table_1")
// info = { roomId: "game_table_1", members: [...], count: 3, maxMembers: 4, state: {...} }SetRoomState — 设置房间状态
为房间设置自定义键值对状态,用于存储游戏阶段、庄家、轮次等信息。
javascript
ws.SetRoomState("game_table_1", "phase", "waiting")
ws.SetRoomState("game_table_1", "dealer", "player1")| 参数 | 类型 | 说明 |
|---|---|---|
| roomID | string | 房间 ID |
| key | string | 状态键名 |
| value | any | 状态值 |
GetRoomState — 获取房间状态
读取房间自定义状态,不存在返回 null。
javascript
var phase = ws.GetRoomState("game_table_1", "phase")状态存储
ws 模块提供全局键值存储,支持内存和 Redis 两种后端,通过配置文件 ws.store_type 切换。
memory(默认)— 数据存储在进程内存中,重启后丢失,性能最优redis— 数据持久化到 Redis,重启不丢失,支持多实例部署
Store — 存储状态
javascript
ws.Store("game:room1", JSON.stringify({
players: ["p1", "p2", "p3", "p4"],
currentPlayer: "p1",
phase: "draw"
}))| 参数 | 类型 | 说明 |
|---|---|---|
| key | string | 存储键名 |
| value | any | 存储值(建议复杂对象使用 JSON.stringify) |
Load — 读取状态
读取存储的状态,不存在返回 null。
javascript
var state = JSON.parse(ws.Load("game:room1"))Exists — 检查状态是否存在
javascript
if (ws.Exists("game:room1")) {
// 游戏房间已存在
}Remove — 删除状态
javascript
ws.Remove("game:room1")StoreKeys — 按前缀查找 key 列表
javascript
var keys = ws.StoreKeys("game:")
// keys = ["game:room1", "game:room2", ...]StoreDelByPrefix — 按前缀批量删除
javascript
ws.StoreDelByPrefix("game:")游戏结束时批量清理所有相关状态:
javascript
ws.StoreDelByPrefix("game:table_001:")定时器
定时器用于实现回合计时、超时自动操作等功能。到期后在服务端直接执行对应的 WS 脚本,无需依赖客户端响应。
SetTimer — 设置定时器
seconds 秒后自动执行 ws/{action} 脚本,脚本中可通过 ws.Param().timerTrigger 判断是否为定时器触发。
javascript
ws.SetTimer("turn:room1:player1", 30, "auto_pass", { roomId: "room1" })| 参数 | 类型 | 说明 |
|---|---|---|
| timerID | string | 定时器唯一标识 |
| seconds | int | 超时秒数 |
| action | string | 到期后执行的 WS 脚本 action |
| data | any | 传递给脚本的额外数据 |
CancelTimer — 取消定时器
javascript
ws.CancelTimer("turn:room1:player1")玩家操作后取消倒计时:
javascript
// 玩家出牌后取消超时定时器
ws.CancelTimer("turn:" + roomId + ":" + uid)定时器触发流程
SetTimer("turn:room1:p1", 30, "auto_pass", {roomId: "room1"})
↓ 30秒后
服务端自动执行 ws/auto_pass 脚本
↓
脚本中 ws.Param() 返回:
{
action: "auto_pass",
timerData: { roomId: "room1" },
timerTrigger: true,
roomId: "room1"
}并发安全
在多人实时操作场景(如游戏出牌)中,多个玩家可能同时操作同一状态,需要加锁防止数据错乱。
Lock — 加锁
对指定 key 加锁,返回 true 表示加锁成功。内存后端为阻塞式锁,Redis 后端为非阻塞式(获取失败立即返回 false)。
javascript
var ok = ws.Lock("game:room1")Unlock — 解锁
javascript
ws.Unlock("game:room1")使用模式
javascript
if (ws.Lock("game:room1")) {
var state = JSON.parse(ws.Load("game:room1"))
// 修改状态...
state.currentPlayer = nextPlayer
ws.Store("game:room1", JSON.stringify(state))
ws.Unlock("game:room1")
}注意
务必在 Lock 成功后使用 Unlock 解锁,避免死锁。Redis 后端的锁有 30 秒自动过期保护。
配置
在 config.yaml 中配置 WebSocket 存储后端:
yaml
ws:
store_type: memory # 内存模式(默认),重启后数据丢失
# store_type: redis # Redis 持久化模式,数据不丢失,需配置 redis 连接Redis 模式
切换到 Redis 后端时,需要同时配置 Redis 连接信息:
yaml
redis:
host: 127.0.0.1
port: 6379
password: ""
database: 0
ws:
store_type: redisRedis 模式适用于:
- 多实例部署(状态跨进程共享)
- 数据持久化(重启不丢失)
- 分布式锁(跨进程互斥)
完整示例
聊天室
javascript
var ws = require("ws")
var uid = ws.Current()
ws.Send(uid, {
action: "order_update",
data: {
orderId: "ORD001",
status: "已发货"
}
})
ws.SendAll({
action: "system_notice",
data: {
message: "系统将于今晚 22:00 进行维护"
}
})
ws.SendRoom("chat_room_1", {
action: "new_message",
data: {
from: uid,
content: ws.Param().message,
time: now()
}
})游戏房间(创建与加入)
javascript
// ws/create_room 脚本
var ws = require("ws")
var uid = ws.Current()
var param = ws.Param()
var roomId = "game_" + uuid()
ws.CreateRoom(roomId, 4)
ws.Store("game:" + roomId, JSON.stringify({
roomId: roomId,
status: "waiting",
players: [uid],
settings: param.settings || {}
}))
ws.Send(uid, {
action: "room_created",
roomId: roomId
})游戏操作(出牌 + 并发安全 + 定时器)
javascript
// ws/play_card 脚本
var ws = require("ws")
var uid = ws.Current()
var param = ws.Param()
var roomId = param.roomId
var card = param.card
if (!ws.Lock("game:" + roomId)) {
ws.Send(uid, { action: "error", message: "操作冲突,请重试" })
return
}
var state = JSON.parse(ws.Load("game:" + roomId))
if (state.currentPlayer !== uid) {
ws.Unlock("game:" + roomId)
ws.Send(uid, { action: "error", message: "还没轮到你" })
return
}
state.hands[uid].splice(state.hands[uid].indexOf(card), 1)
state.discards[uid].push(card)
state.lastCard = card
ws.Store("game:" + roomId, JSON.stringify(state))
ws.Unlock("game:" + roomId)
ws.CancelTimer("turn:" + roomId + ":" + uid)
ws.SendRoom(roomId, {
action: "card_played",
uid: uid,
card: card
})
var nextUid = getNextPlayer(uid, state)
ws.SetTimer("turn:" + roomId + ":" + nextUid, 30, "auto_pass", { roomId: roomId })定时器自动操作
javascript
// ws/auto_pass 脚本
var ws = require("ws")
var param = ws.Param()
var roomId = param.roomId
if (ws.Lock("game:" + roomId)) {
var state = JSON.parse(ws.Load("game:" + roomId))
state.phase = "next_turn"
ws.Store("game:" + roomId, JSON.stringify(state))
ws.Unlock("game:" + roomId)
ws.SendRoom(roomId, {
action: "player_passed",
uid: ws.Current(),
reason: "timeout"
})
}注意事项
- 确保目标用户已建立 WebSocket 连接,否则消息无法送达
- 消息内容建议统一使用
{action, data}结构,便于前端解析 - 广播消息会影响所有在线用户,请谨慎使用
- 复杂对象存储时建议使用
JSON.stringify/JSON.parse Lock成功后务必Unlock,避免死锁- 定时器到期后在服务端直接执行脚本,不依赖客户端响应
- 多实例部署时必须使用
ws.store_type: redis,否则状态无法跨进程共享