Skip to content

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: "您有一条新消息"
    }
})
参数类型说明
uidstring目标用户 UID
contentmap消息内容

SendAll — 广播消息 ​

向所有在线用户广播消息。

javascript
ws.SendAll({
    action: "system",
    data: {
        message: "系统维护通知"
    }
})

SendRoom — 向房间发送消息 ​

向指定房间内的所有用户发送消息。

javascript
ws.SendRoom("room_001", {
    action: "chat",
    data: {
        from: "张三",
        message: "大家好"
    }
})
参数类型说明
roomIDstring房间 ID
contentmap消息内容

房间管理 ​

Join — 加入房间 ​

javascript
ws.Join("room_001")

Leave — 离开房间 ​

javascript
ws.Leave("room_001")

CreateRoom — 创建房间并加入 ​

创建房间并自动将当前用户加入。maxMembers 为 0 表示不限制人数。

javascript
ws.CreateRoom("game_table_1", 4)
参数类型说明
roomIDstring房间 ID
maxMembersint最大成员数,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")
参数类型说明
roomIDstring房间 ID
keystring状态键名
valueany状态值

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"
}))
参数类型说明
keystring存储键名
valueany存储值(建议复杂对象使用 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" })
参数类型说明
timerIDstring定时器唯一标识
secondsint超时秒数
actionstring到期后执行的 WS 脚本 action
dataany传递给脚本的额外数据

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: redis

Redis 模式适用于:

  • 多实例部署(状态跨进程共享)
  • 数据持久化(重启不丢失)
  • 分布式锁(跨进程互斥)

完整示例 ​

聊天室 ​

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,否则状态无法跨进程共享

版权所有.