服务端接口
你的服务端用 App Token 管理游戏房间:查大区、建房、签发加入票据、开局、结束、移出玩家、查询房间和成员。玩家加入房间和连接游戏节点由客户端 SDK 完成,不在这里。下文的接口省略了应用前缀,完整地址为 $IM_API/$ORG/$APP 加上表中的路径,鉴权见鉴权与 App Token。
最常用的三步——建房、签票据、结束:
curl -X PUT "$IM_API/$ORG/$APP/game/rooms/rank-8812" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{"max_players":4,"start_mode":"all_ready","ext":{"match_id":"m-8812"}}'
curl -X POST "$IM_API/$ORG/$APP/game/rooms/rank-8812/tickets" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{"users":[{"username":"alice"},{"username":"bob"},{"username":"carol"},{"username":"dave"}]}'
curl -X POST "$IM_API/$ORG/$APP/game/rooms/rank-8812/close" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{"result":{"winner":"alice"}}'接口一览
| 要做什么 | 方法和路径 | 能否重试 |
|---|---|---|
| 查可用大区 | GET /game/regions | 可以 |
| 创建房间 | PUT /game/rooms/{room_id} | 可以,同参数返回同一房间 |
| 签发加入票据 | POST /game/rooms/{room_id}/tickets | 不自动重试;超时后直接重新签发 |
| 开局 | POST /game/rooms/{room_id}/start | 可以 |
| 结束房间 | POST /game/rooms/{room_id}/close | 可以,只有第一次生效 |
| 移出玩家 | POST /game/rooms/{room_id}/members/{username}/remove | 可以,用同一个 operation_id |
| 查询房间 | GET /game/rooms/{room_id} | 可以 |
| 列出房间 | GET /game/rooms | 可以 |
| 列出成员 | GET /game/rooms/{room_id}/members | 可以 |
服务端 SDK
四种服务端 SDK 都有 game 模块,方法和接口一一对应,自动换取 App Token、重试可以重试的请求:
| 接口 | Go | Node.js | Python | Java |
|---|---|---|---|---|
| 查大区 | client.Game.ListRegions | client.game.listRegions | client.game.list_regions | im.game().listRegions |
| 创建房间 | Game.CreateRoom | game.createRoom | game.create_room | game().createRoom |
| 签发票据 | Game.IssueTickets | game.issueTickets | game.issue_tickets | game().issueTickets |
| 开局 | Game.StartRoom | game.startRoom | game.start_room | game().startRoom |
| 结束房间 | Game.CloseRoom | game.closeRoom | game.close_room | game().closeRoom |
| 移出玩家 | Game.RemoveMember | game.removeMember | game.remove_member | game().removeMember |
| 查询房间 | Game.GetRoom | game.getRoom | game.get_room | game().getRoom |
| 列出房间 | Game.ListRooms、AllRooms | game.listRooms、iterRooms | game.list_rooms、iter_rooms | game().listRooms |
| 列出成员 | Game.ListMembers | game.listMembers | game.list_members | game().listMembers |
Node.js 示例——匹配成功后建房、签票据,对局结束时结算:
import { DRClient, isDRError } from '@deeprespond/im-server-sdk';
const im = new DRClient({ baseUrl: process.env.IM_API!, orgName: 'demo-org', appName: 'demo', clientId: process.env.IM_CLIENT_ID!, clientSecret: process.env.IM_CLIENT_SECRET! });
export async function startMatch(matchId: string, players: string[]) {
try {
// 房间 ID 用匹配 ID:超时重试也只会有一个房间
await im.game.createRoom(matchId, { max_players: players.length, start_mode: 'all_ready', ext: { match_id: matchId } });
} catch (err) {
if (isDRError(err) && err.reason === 'game_capacity_unavailable') throw new Error('游戏服务繁忙,请稍后重试');
throw err;
}
const tickets = await im.game.issueTickets(matchId, { users: players.map((username, seat) => ({ username, seat })) });
return Object.fromEntries(tickets.map((t) => [t.username, t.ticket])); // 分别交给每个玩家
}
export async function finishMatch(matchId: string, winner: string) {
const { accepted } = await im.game.closeRoom(matchId, { reason: '对局结束', result: { winner } });
return accepted; // false 表示房间之前已经在结束
}各语言的完整示例、方法签名和错误处理:Go、Node.js、Python、Java;安装和初始化见服务端 SDK。下面按接口说明请求和返回。
查大区
GET /game/regions
{"items": [{"region": "cn-east", "name": "华东", "default": true, "available": true}]}| 字段 | 说明 |
|---|---|
region | 大区 ID,建房时用 |
default | 是否为应用的默认大区;建房不指定大区时用它 |
available | 当前是否有可调度的游戏节点。为 false 时在这个大区建房会失败 |
创建房间
PUT /game/rooms/{room_id},room_id 为 1–64 位字母、数字、_、-,区分大小写。请求体的字段都可以省略(省略时发 {}):
| 字段 | 默认 | 说明 |
|---|---|---|
name | 空 | 房间名,最多 128 个字,只在控制台显示 |
region | 应用的默认大区 | 大区 ID。与 regions 二选一 |
regions | — | 1–3 个候选大区,按顺序尝试,第一个有可用节点的生效。适合按玩家所在地区排序 |
max_players | 10 | 座位数,1–10 |
sync_mode | frame | frame 帧同步;relay 只转发自定义消息,见同步方式 |
tick_rate | 30 | 帧率:10、15、20、30、60。relay 时忽略。帧率越高延迟越低,流量越大 |
start_mode | auto | auto 第一个玩家连上就开局;all_ready 已连上的玩家都准备后开局;manual 由你调用开局 |
access_mode | ticket | ticket 凭你签发的票据加入;open 本应用的任何登录用户都能加入,不需要票据 |
max_duration_seconds | 3600 | 最长时长,至少 60,不超过平台上限(控制台「游戏房间」页可以看到)。到时自动结束(expired) |
reconnect_window_seconds | 60 | 断线后保留座位的时间,0–300;0 表示断线即离开 |
snapshot | 关闭 | {"enabled": true, "interval_seconds": 30}:帧同步房间的状态快照,玩家断线太久、缺的帧太多时用快照追上进度。间隔 10–120 秒。relay 不能开启 |
ext | {} | 你的扩展字段(JSON 对象),不超过 2KB,在回调里原样带回。适合放对局 ID、模式 |
返回:201 新建,200 同参数的房间已经存在(重复请求),两种情况都返回 {"room": Room}。房间的字段见房间。
房间 ID 的规则:同一个 ID、同样的参数重复创建,返回同一个房间;参数不同,或者这个 ID 的房间已经结束,返回 409 conflict(room_id_used)。所以用对局 ID 作为房间 ID 时,匹配服务可以放心重试。
签发加入票据
POST /game/rooms/{room_id}/tickets
{"users": [{"username": "alice", "seat": 0}, {"username": "bob"}], "ttl_seconds": 120}| 字段 | 说明 |
|---|---|
users | 1–10 个用户。seat 可选,指定座位(0 到 max_players-1);不指定时,玩家加入时分到最小的空座位 |
ttl_seconds | 有效期,30–600 秒,默认 120 |
返回 201:
{"tickets": [
{"username": "alice", "seat": 0, "ticket": "gtk_9f3KqZ1mXb2…", "expires_at": "2026-11-10T08:02:00.000Z"},
{"username": "bob", "seat": null, "ticket": "gtk_Hq7Lm0Pz4c…", "expires_at": "2026-11-10T08:02:00.000Z"}
]}ticket原文只在这里返回一次。通过你的业务接口交给对应的玩家,不要写进日志。- 票据只能用一次,只对这个用户和这个房间有效。
- 同一用户再签一次,他之前没用的票据作废。所以请求超时后直接重新签发即可,服务端 SDK 也不会自动重试这个接口。
open房间不需要票据,签发返回400。
开局
POST /game/rooms/{room_id}/start,没有请求体。
只用于 start_mode 为 manual 的房间。游戏节点开始推进帧后,房间状态才变为 playing,所以返回时 room.status 可能还是 open;已经在对局中的直接返回。
结束房间
POST /game/rooms/{room_id}/close
{"reason": "对局结束", "result": {"winner": "alice", "scores": {"alice": 12, "bob": 9}}}| 字段 | 说明 |
|---|---|
reason | 可选,最多 200 字,只写操作日志,玩家看不到 |
result | 可选,你的结算结果(JSON 对象),不超过 4KB。原样发给房间里的每个玩家(客户端 onClose 的 result),并出现在 game_room.closed 回调里 |
返回:202 表示这次开始结束,房间进入 closing;游戏节点处理完后变为 closed,并发出 game_room.closed。200 表示房间之前已经在结束或已经结束,返回当前的房间,不修改第一次的原因和结果。
不调用这个接口,房间也会在超时、所有人离开后自动结束,见房间的状态。
移出玩家
POST /game/rooms/{room_id}/members/{username}/remove
{"operation_id": "kick-8812-bob-1", "reason": "作弊"}| 字段 | 说明 |
|---|---|
operation_id | 幂等键,1–64 位字母、数字或 _ . : -。同一个 operation_id 重复请求返回第一次的结果。服务端 SDK 不传时自动生成,重试时不变 |
reason | 可选,最多 200 字,写操作日志 |
返回:202 {"operation_id": "…", "member": Member} 表示已移出;200 {"operation_id": "…", "member": null} 表示这个用户不在房间里。
被移出的玩家立即断开,客户端 onClose 的 reason 为 kicked,SDK 不会自动重连;他没用的票据也作废。要让他回来,重新签发票据。
查询房间
GET /game/rooms/{room_id},返回 {"room": Room}。
列出房间
GET /game/rooms?status=open,playing&search=rank-&limit=20&cursor=…
| 参数 | 说明 |
|---|---|
status | open、playing、closing、closed,多个用逗号分隔;不传为全部 |
search | 房间 ID 前缀 |
limit、cursor | 分页,limit 1–100,默认 20;下一页用返回的 next_cursor,为 null 时没有更多 |
按创建时间从新到旧,返回 {"items": [Room], "next_cursor": "…"}。
列出成员
GET /game/rooms/{room_id}/members,返回 {"items": [Member]}。玩家每加入一次一项:同一座位换了人、同一个人离开后再加入,都会有多项,按加入顺序。
房间
| 字段 | 说明 |
|---|---|
room_id、name | 房间 ID 和名称 |
status | open、playing、closing、closed |
region、region_name | 所在大区 |
max_players、player_count | 座位数、当前在座的人数 |
sync_mode、tick_rate、start_mode、access_mode | 创建时的设置;relay 房间的 tick_rate 为 0 |
max_duration_seconds、reconnect_window_seconds、snapshot | 创建时的设置 |
ext | 创建时的扩展字段 |
version | 房间版本,每次状态变化加一;用来判断回调的新旧 |
created_at、started_at、closed_at | 时间(ISO 8601,UTC);没有开局或没有结束时为 null |
close_reason | 结束原因,见房间的状态;没有结束时为空字符串 |
result | 结束时的结算结果;没有时为 null |
成员
| 字段 | 说明 |
|---|---|
seat、username | 座位和用户名 |
status | joined 已拿到座位还没连上;connected 在线;disconnected 断线中(重连窗口内);left 已离开 |
transport | 连接方式:udp 或 ws;没连上过为空字符串 |
rtt_ms | 往返延迟的中位数(毫秒),只在 connected 时有 |
connected_ms | 累计在线时长(毫秒),计费的依据 |
joined_at、left_at | 加入、离开的时间 |
leave_reason | leave 主动离开;timeout 断线超过重连窗口;removed、kicked 被移出;connect_timeout 拿到座位后 60 秒内没有连上;room_closed 房间结束 |
错误
错误的格式见错误码。游戏房间的具体原因在 details.reason:
| HTTP | code | details.reason | 原因和处理 |
|---|---|---|---|
| 403 | permission_denied | game_disabled | 平台没有开放游戏房间,或单独关闭了你的应用。联系我们 |
| 403 | permission_denied | plan_feature_disabled | 套餐不含游戏房间。升级套餐 |
| 400 | invalid_argument | region_unavailable | 大区不存在或已停用。用查大区返回的大区 |
| 400 | invalid_argument | — | 参数不合法,message 说明是哪个字段;签发票据时用户不存在或已停用(details.usernames)、同一批指定了重复座位(details.seat) |
| 404 | not_found | — | 房间不存在 |
| 409 | conflict | room_id_used | 房间 ID 已用于参数不同的房间,或这个房间已经结束。换一个 ID |
| 409 | conflict | room_not_open | 房间已在结束或已结束,不能再签票据、开局 |
| 409 | conflict | operation_id_used | operation_id 已用于别的移出操作。换一个 |
| 409 | limit_exceeded | open_rooms | 同时进行的房间数达到上限。结束不用的房间,或联系我们提高上限 |
| 503 | unavailable | game_capacity_unavailable | 大区暂时没有可用的游戏节点。稍后重试,或用 regions 给出候选大区 |
| 503 | unavailable | game_node_unavailable | 房间所在的游戏节点暂时不可用 |
