读取公开状态
无需账号即可显示节点在线状态、容量和公开房间。
查看目录接口 →通过标准 JSON-RPC 2.0 接口添加节点、查询房间并连接游戏。启动器只需管理一个本地桥接进程,不必实现 ICE 打洞或中继协议。
推荐通过启动器随附或安装的 Buggcraft bridge 子进程接入。进程负责节点身份、房间信令、P2P 与中继切换。
每行发送一个 JSON-RPC 2.0 请求,每个请求对应一行响应。先初始化协议,再添加节点,之后创建或加入房间。
开发环境可用 Python 3.11+ 执行 python -m hookup bridge;发布时把桥接组件随启动器一起安装或打包。
调用 bridge.initialize,再用 node.add 导入用户从可信节点取得的接入码。桥接进程负责凭据,不要把凭据写入日志。
用 room.list 展示公开房间,或让用户输入邀请码后调用 room.join。加入者可以选择 P2P 优先或仅中继。
把返回的 127.0.0.1:端口 交给 Minecraft。退出时调用 room.close;进程关闭时会清理剩余会话。
PYTHONPATH=buggcraft/src/buggcraft python -m hookup bridge{"jsonrpc":"2.0","id":1,"method":"bridge.initialize","params":{"protocol":1}}
{"jsonrpc":"2.0","id":2,"method":"node.add","params":{"code":"buggnode1-<用户接入码>"}}
{"jsonrpc":"2.0","id":3,"method":"room.join","params":{"node":"<返回的节点句柄>","invite":"bugg1-<房间邀请码>","transport":"auto"}}标准报文采用 JSON-RPC 2.0;方法名、参数、限制和错误码由 Buggcraft 的 v1 profile 定义。该参考实现目前不支持 JSON-RPC batch 请求。发布第三方启动器时,请自行部署节点并用真实客户端验证。
公开状态走 HTTPS。桥接接口提供有凭据的目录操作;游戏数据不经过目录查询接口。
| 接口 | 用途 |
|---|---|
GET /api/status | 读取节点公开状态、容量、在线人数和注册开关。 |
node.add | 验证并导入接入码,返回进程内使用的不透明节点句柄。 |
room.list | 分页获取公开房间;传入 offset,最多返回 50 项。 |
session.list | 读取本进程活动会话及当前连接状态。 |
account_lookup | 个人身份可按用户名或账号 ID 查询;匿名凭据没有好友查询权限。 |
单行输入上限 8 KiB;错误使用 JSON-RPC 标准错误结构。超时、节点不可达或凭据失效时提示用户,并采用有上限的退避重试。
其他联邦入口可以继续提供目录。房间归属节点不可用时房主需重建房间;已经建立的 Minecraft TCP 会话不会自动迁移到另一台服务器。
路径由每位加入者分别选择和协商。一个访客 P2P 失败或主动选中继,不会让房间里的其他访客改变路径。
| 方法 | 参数与结果 |
|---|---|
room.host | node、game_port、transport;返回会话 ID、邀请码和房间状态。 |
room.join | node、invite、transport;返回会话 ID、本地回环地址和连接状态。 |
room.close | 使用会话 ID 关闭监听、P2P 连接、中继连接与后台任务。 |
session.list | 返回本进程中的活动会话、角色与当前传输状态。 |
transport:auto(默认)会先尝试 WebRTC ICE / DTLS / SCTP 直连,连不上时该玩家自动走 SSH 中继。transport:relay 会跳过 P2P,仅使用中继。
P2P 需要双方网络允许 ICE 建连;对称 NAT、防火墙、UDP 阻断等环境可能只能中继。当前节点提供 STUN 地址发现和流量中继,不提供 TURN 服务,也不能保证所有网络都可直连。
P2P 建连会让通信双方获知彼此的网络候选地址。若用户不愿向房间对端提供自己的网络地址,选“仅通过节点中转”。中继能隐藏玩家之间的直接地址,但中继节点仍会看到连接端点和转发流量。
匿名接入可以添加节点和加入房间,但不显示好友入口。好友需要账号身份、对方同意以及可达的联邦目录。
联邦节点同步公开用户名、账号 ID 和公钥。用户名目录不包含密码;查询结果不代表已经获得好友或房间权限。
跨节点登录需要用户启用并配置加密身份备份。没有备份时,账号登录仍依赖注册节点在线;仅同步用户名不能恢复账号。
{"op":"account_lookup","identifier":"alex"}有效期最长 7 天。持有该码的设备可代表账号操作;使用系统安全存储保护接入码,不要写入日志、崩溃报告或同步到公开配置文件。
下列为应用内调用片段;接入码、邀请码和游戏启动函数由你的启动器提供。C/C++ 例子中的 BridgeProcess 是启动器自身的双向进程管道封装,Java 例子使用 Jackson,C# 使用 System.Text.Json。协议请求的格式和顺序相同。
import json, subprocess
p = subprocess.Popen(["python", "-m", "hookup", "bridge"], stdin=subprocess.PIPE,
stdout=subprocess.PIPE, text=True, encoding="utf-8")
def rpc(i, method, params):
p.stdin.write(json.dumps({"jsonrpc":"2.0", "id":i,
"method":method, "params":params}) + "\n")
p.stdin.flush(); reply = json.loads(p.stdout.readline())
if "error" in reply: raise RuntimeError(reply["error"]["message"])
return reply["result"]
rpc(1, "bridge.initialize", {"protocol":1})
node = rpc(2, "node.add", {"code": access_code})["node"]
room = rpc(3, "room.join", {"node":node, "invite":invite, "transport":"auto"})
launch_minecraft(room["address"])
rpc(4, "room.close", {"session":room["session"]})// Use CreateProcess pipes (Windows) or pipe/posix_spawn (Unix) for the bridge process.
// Keep stderr separate; write_line/read_line exchange one UTF-8 JSON frame each.
#include <nlohmann/json.hpp>
using json = nlohmann::json;
json rpc(BridgeProcess& bridge, int id, const char* method, json params) {
json request={{"jsonrpc","2.0"},{"id",id},{"method",method},{"params",params}};
bridge.write_line(request.dump());
auto response=json::parse(bridge.read_line(8192));
if (response.contains("error")) throw std::runtime_error(response["error"]["message"].get<std::string>());
return response.at("result");
}
auto init=rpc(bridge,1,"bridge.initialize",{{"protocol",1}});
auto node=rpc(bridge,2,"node.add",{{"code",access_code}}).at("node");
auto room=rpc(bridge,3,"room.join",{{"node",node},{"invite",invite},{"transport","auto"}});
launch_minecraft(room.at("address").get<std::string>());
rpc(bridge,4,"room.close",{{"session",room.at("session")}});var process = new ProcessBuilder(pythonPath, "-m", "hookup", "bridge").start();
var in = new java.io.BufferedWriter(new java.io.OutputStreamWriter(process.getOutputStream(), java.nio.charset.StandardCharsets.UTF_8));
var out = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream(), java.nio.charset.StandardCharsets.UTF_8));
var json = new com.fasterxml.jackson.databind.ObjectMapper();
var sequence = new java.util.concurrent.atomic.AtomicInteger();
java.util.function.BiFunction<String, java.util.Map<String,Object>, com.fasterxml.jackson.databind.JsonNode> rpc = (method, params) -> {
try {
var q=java.util.Map.of("jsonrpc","2.0", "id",sequence.incrementAndGet(), "method",method, "params",params);
in.write(json.writeValueAsString(q)); in.newLine(); in.flush();
var reply=json.readTree(out.readLine());
if (reply.has("error")) throw new RuntimeException(reply.get("error").toString());
return reply.get("result");
} catch (java.io.IOException e) { throw new RuntimeException(e); }
};
rpc.apply("bridge.initialize", java.util.Map.of("protocol",1));
var node=rpc.apply("node.add", java.util.Map.of("code",accessCode)).get("node").asText();
var room=rpc.apply("room.join", java.util.Map.of("node",node, "invite",invite, "transport","auto"));
launchMinecraft(room.get("address").asText());
rpc.apply("room.close", java.util.Map.of("session",room.get("session").asText()));var start = new System.Diagnostics.ProcessStartInfo(pythonPath, "-m hookup bridge") {
RedirectStandardInput=true, RedirectStandardOutput=true, UseShellExecute=false, CreateNoWindow=true };
using var bridge = System.Diagnostics.Process.Start(start)!;
async System.Threading.Tasks.Task<System.Text.Json.JsonElement> Rpc(int id, string method, object parameters) {
var q=System.Text.Json.JsonSerializer.Serialize(new { jsonrpc="2.0", id, method, @params=parameters });
await bridge.StandardInput.WriteLineAsync(q); await bridge.StandardInput.FlushAsync();
using var doc=System.Text.Json.JsonDocument.Parse((await bridge.StandardOutput.ReadLineAsync())!);
var root=doc.RootElement; if (root.TryGetProperty("error",out var error)) throw new Exception(error.ToString());
return root.GetProperty("result").Clone();
}
await Rpc(1,"bridge.initialize",new { protocol=1 });
var node=(await Rpc(2,"node.add",new { code=accessCode })).GetProperty("node").GetString();
var room=await Rpc(3,"room.join",new { node, invite, transport="auto" });
LaunchMinecraft(room.GetProperty("address").GetString());
await Rpc(4,"room.close",new { session=room.GetProperty("session").GetString() });连接能否直达取决于两端网络、路由器、防火墙及 UDP 状态。请分别测试 P2P 成功、UDP 被阻断后中继、手动仅中继、断线清理和多名玩家混合路径。
阅读协议细节 →请在自己部署的节点上完成端到端测试,并明确告诉用户连接状态和路径。