StrataProxy
StrataProxy 是面向 Minecraft 1.7.10 Forge 的玩家会话代理。项目正在从零重写;当前分支已有离线和在线模式的登录、协议转发与跨后端切换路径,尚未达到生产可用标准。
核心边界
- 玩家会话:代理负责入服、连接后端、转发、切换后端和断线清理。玩家身份包含 UUID 与连接代次,避免旧连接的异步结果作用到新连接。在线玩家视图由 Session 持有,监听器只保留按 UUID 查找会话的索引。
- 服务器目录:静态配置与插件调用同一个注册接口。
ServerView只包含名称、地址、标签和插件元数据。目录不采集或暴露后端负载、连接占用和容量;玩法插件自行获取选服所需的数据。同名重注册生成新句柄,旧异步结果不能修改新条目。一次连接使用选定时的地址快照,后续注册更新只影响新选择。 - 插件 API:插件可查询
PlayerView、ServerView,动态注册后端,并以一个异步回调决定初始落点。空岛分配、实例唤醒和玩法数据由插件自行实现。单次选服失败或超时只结束当前请求,不停用插件;玩家断线会取消未决请求并移除尚未执行的回调,已启动的插件异步任务只做尽力取消。宿主关闭时会结束未决选服请求并撤销插件注册。插件返回的拒绝原因会作为登录断开消息发给玩家。 - 发现方式:DNS 是独立插件,核心不认识 DNS 或 Agent。配置只启用列出的插件;插件关闭时其注册会清理。普通 DNS 主机名通过独立解析器查询 A 与 AAAA 地址,不使用 JVM 的进程级地址缓存;
localhost按系统回环地址解析。最小默认选路按注册顺序选取后端,因此 DNS 插件先注册 IPv4 地址。一次查询失败会保留已有注册,连续三次失败会移除旧地址,恢复解析后重新注册。 - 协议热路径:同一玩家的前端和后端通道绑定同一个 Netty 事件循环,会话控制状态在该循环上串行更新。会话始终保留协议 5 的帧边界,普通数据包不重新编码并尽量复用 Netty
ByteBuf;Keep Alive ID 在会话内转换,可同时跟踪多个未回复请求,转服后旧后端的迟到回复会被丢弃;转服后只改写少数与玩家实体 ID 相关的包,并按目标通道可写状态控制读取。后端主机名由 Netty 异步解析,避免把 DNS 等待放在玩家 I/O 线程上。 - 日志:SLF4J API 与 Logback 运行时。
当前可运行范围
配置必须显式选择 authentication: OFFLINE 或 ONLINE_BUNGEE。两种模式均解析协议 5 Handshake 与 Login Start,等待异步落点,连接后端,确认 Login Success,然后转发普通字节流。在线模式执行加密握手、异步会话校验,并向可信的 Bungee 兼容后端转发已验证身份。状态查询由代理直接回答:在线数是本代理已登录的会话数;协议必需的最大值至少为 1,且不低于在线数,不代表后端容量。登录阶段的后端拒绝消息会在前端写出后再清理会话;后端关闭或读失败时,已提交给客户端的写入也会先排空再关闭前端,这两种等待最多 5 秒。Players.transfer 会在旧后端继续服务时登录候选后端;普通候选后端收到 Join Game 后即可开始交接,Forge 候选后端收到 ServerHello 后先切换握手链路,待客户端完成握手并收到 Join Game 时再合成 Respawn。NETWORK_READY 等待这次协议握手和世界切换包写出,但不表示目标服的插件已完成空岛或副本加载。切换后的玩家实体 ID 会映射到原客户端 ID;候选登录失败时保留旧链路。Forge 重置包写出后若目标拒绝握手,代理结束会话并返回失败,不能恢复旧链路。DNS 与 Agent 是调用通用注册 API 的独立插件。
验证边界:跨后端切换已通过普通协议与 Forge 握手的合成 TCP 测试,包括切换前失败回退、候选服 Join Game 后立即断开时保留旧会话、切换后 Forge 握手拒绝、维度变化后再转服、Forge 到普通后端的切换、握手后 Join Game、维度覆盖、玩家实体 ID 映射和中途断线清理。从 Forge 切换到普通后端会发送一次 FML 重置以恢复客户端注册表;随后进入 Forge 时不会重复发送重置。转服固定发送中间维度和目标维度两次 Respawn,避免依赖已经过期的客户端维度记录;交接期间客户端与旧后端的帧分别进入有上限的缓冲区。成功时,客户端帧在世界切换包写出后转发,旧后端帧丢弃;切换前失败恢复旧链路后,两侧暂存的帧都会回放。这些时序已通过合成 TCP 测试。在线认证及其加密连接上的 Forge 转服已通过注入会话校验器的合成 TCP 测试,尚未调用真实 Mojang 会话服务。最小 Uranium 1.7.10 后端已通过代理完成协议探针登录和保持连接,两台 Uranium 间的 Forge 转服探针也已通过。Prism 中的真实 Minecraft 1.7.10 + Forge 10.13.4.1614 客户端已通过最小 Uranium 的首次登录与两台 Uranium 间的插件触发转服,见 真实客户端联机记录。目标 1.7.10 整合包、Mojang 在线认证、跨主机部署和目标流量下的性能验收仍未验证;当前不能宣称生产可用或目标整合包实服兼容。
配置与运行
默认配置:proxy-core/src/main/resources/config/strataproxy.yml。安装包包含静态后端示例和 strataproxy-dns.example.yml;后者通过 plugins.enabled 启用 DNS 插件,backends: [] 表示不使用静态后端。相对插件目录从配置文件所在目录解析;安装包示例中的 ../plugins 指向同一安装包的插件目录。ONLINE_BUNGEE 只能连接已启用旧版 Bungee 身份转发、且限制直连的可信后端。
--validate-config 检查核心配置和静态后端注册;启用插件的自定义设置由插件在启动时检查。
plugins.initialPlacementTimeoutSeconds 控制初始选服回调的等待时间,默认 15 秒,可配置为 1–120 秒。握手与身份验证、后端连接与登录各有独立的 15 秒期限;选服阶段不会消耗这两个阶段的时间。后端 Login Success 后,客户端必须在 2 分钟内完成初次 PLAY/Forge 握手,否则代理断开会话。插件可以异步查询或准备后端;超过时限的当前玩家登录会失败,后续玩家仍可调用该插件。客户端和后端自身也可能提前断开等待中的连接。
未认证客户端的单帧上限为 4 KiB;后端确认 Login Success 后恢复协议 5 的 PLAY 帧上限。在线模式若已验证的玩家资料无法编码为后端转发握手,会立即结束本次登录。
在线模式最多同时保留 256 个 Mojang 会话校验请求(包括等待响应和解析的请求);超过上限的新登录会立即失败,避免请求与解析任务无限积压。此限制只约束代理的认证资源,与后端负载无关。
候选后端就绪后的切换阶段另有 15 秒期限。如果旧链路的写入持续未完成,代理会以失败结果结束转服并断开该会话,避免玩家与插件调用无限等待。Forge 切换包写出后,目标握手和 Join Game 最多再等待 30 秒;超时会结束玩家会话并返回失败。
.\gradlew.bat :proxy-core:installDist
.\proxy-core\build\install\strataproxy\bin\strataproxy.bat --validate-config .\proxy-core\build\install\strataproxy\config\strataproxy.yml
.\proxy-core\build\install\strataproxy\bin\strataproxy.bat --config .\proxy-core\build\install\strataproxy\config\strataproxy.yml
使用 DNS 插件时,把示例配置中的主机名换成自己的 DNS 主机名;相对插件目录随配置文件位置一起解析。A 和 AAAA 记录分别刷新:一个地址族查询超时会暂时保留其上次结果,连续三次失败后只撤销该地址族;明确返回无记录会清除该地址族的缓存。两族均无记录时,目录在连续三次查询失败后撤销旧注册。
Agent 动态注册插件
Agent 插件同样由 plugins.enabled 显式启用。它通过一个独立 HTTP 端点接收实例注册和续租,再调用通用 Servers API;代理核心不包含 Agent 协议或云平台发现逻辑。端点默认只绑定 127.0.0.1:28080,请求使用至少 32 字节的共享密钥做 HMAC-SHA256 签名,带 60 秒时间窗和一次性随机 nonce。请求体、并发请求数、实例数和租约时长都有上限。每个 Agent 进程使用新的 UUID generation;活动租约不能被其他 generation 覆盖。注销或过期的 generation 会保留到最大租约时长加 60 秒签名时间窗之后,再由周期清理器删除;这能拦截在租约切换期间延迟到达的旧请求,同时避免 Agent 重启次数累积导致注册耗尽。异常退出后,租约到期会移除后端。
plugins:
directory: ../plugins
enabled:
dev.strataproxy.plugins.agent.AgentDiscoveryPlugin:
secret: "replace-with-a-private-random-secret-of-32-bytes-or-more"
# host: 127.0.0.1
# port: "28080"
# concurrency: "4"
# maxBodyBytes: "4096"
# maxLeaseSeconds: "60"
# maxInstances: "128"
密钥不要提交到仓库;插件和 Agent 必须使用同一个值。跨主机部署时,应绑定到私有网络,并通过可信 TLS 终止器或私有链路保护传输;HMAC 验证身份和请求完整性,但不加密流量。AgentRegistrationClient 是仅依赖 JDK 的示例客户端:设置 STRATAPROXY_AGENT_SECRET,然后运行 AgentExampleMain <http-endpoint> <agent-id> <backend-name> <tcp-address>,其中端点形如 http://127.0.0.1:28080/registration。示例每 10 秒续租一次,租约 30 秒;网络故障和服务端暂时不可用时继续重试,租约过期导致旧 generation 被拒绝时换用新 generation 注册;认证或请求格式错误则退出。正常退出时尝试注销,异常退出由租约过期清理。Agent 协议只注册后端名称与地址,不需要消息队列。
开发验证
.\gradlew.bat check
.\gradlew.bat :proxy-core:installedDistSmokeTest
.\smoke\installed-discovery.ps1
.\smoke\container-network.ps1
installed-discovery.ps1 在 PowerShell 7 下使用已安装的发行包,以临时配置分别启动 DNS 和 Agent 插件;它验证两种发现方式下玩家完成离线登录和 PLAY 帧转发。Agent 注销时,已连接玩家继续收发 PLAY 帧,新玩家无法选到已注销的后端;原玩家离开后在线数归零。先运行 :proxy-core:installDist 或全量 check 来生成发行包。定向测试覆盖目录代次、插件生命周期与超时、协议边界、relay 背压,以及合成 TCP 登录和切换。最小 Forge 客户端实测已通过;目标整合包尚未提供,合成测试和最小实例测试不能替代目标环境验收。
smoke/container-network.ps1 使用 Docker bridge 中的独立代理、后端和客户端容器,依次检查静态配置、DNS 插件和 Agent 插件的注册、登录及 PLAY 转发;Agent 模式还验证注销后已连接会话继续转发。PowerShell 7 和 Docker Desktop 是运行前提。一次结果见 容器网络烟测记录。它验证跨容器网络与 DNS,不能代替跨物理主机或目标 Forge 整合包实测。
PR 的 .gitea/workflows/verify.yml 独立运行 gradlew check,不使用 Maven 发布凭据。.gitea/workflows/ci.yml 保留默认分支的构建发布入口;发布工作流所需的跨仓库可复用工作流权限和 Maven 凭据由 Gitea 仓库配置提供,不能用本地 check 的结果代替远端发布验收。
如果本地已有 Uranium 1.7.10 可运行包及其编译好的 MinecraftProtocolProbe,可运行 smoke/local-uranium.ps1 -BundlePath <包目录> -ProbeClassesPath <探针类目录>。脚本复制服务端到忽略目录,启动 Java 8 后端与当前安装包,再让探针经代理完成状态查询、FML 登录、Join Game 和持续 Keep Alive,结束时停止两个进程。本地一次结果与具体前提见 最小 Uranium 联机记录。这项测试使用协议探针,目标整合包客户端与实服转服仍待验证。
smoke/local-uranium-transfer.ps1 -BundlePath <包目录> 复制并启动两台 Uranium,然后编译仓库中的协议探针,通过实际 ProxySessionListener 请求从旧服切换到新服。加上 -InstalledPlugin 则启动已安装的 ProxyMain:旧服由静态配置注册,临时插件用 Servers.register 注册目标服,在初始落点回调读取 ServerView,再通过 Players.transfer 发起转服。默认模式使用协议探针核对 FML 重置、重新握手、世界切换包和目标连接的 Keep Alive,见 两台 Uranium 转服记录。-InstalledPlugin -ReturnToOld 让协议探针验证旧服→新服→旧服的两次切换。-InstalledPlugin -PrismClient 改用本机 Prism 中的 1.7.10 Forge 实例,要求客户端在目标服保持连接 10 秒;可同时使用 -ReturnToOld 验证真实客户端往返。两个真实客户端脚本都可用 -PrismInstance <实例名> 选择其他实例。若实例的文件夹名与启动名不同,另传 -PrismInstanceFolder <文件夹名>,以便准确定位并清理这次启动的客户端;-PrismPath 指向其他 Prism 安装位置。smoke/local-uranium.ps1 -PrismClient 可单独验证首次登录。这些实测仍不能替代目标整合包验收。
安装包转服烟测可加 -DebugSession,将当前运行的会话失败路径 DEBUG 日志写入所打印运行目录的 proxy.stdout.log。一次间歇性初始登录 EOF 的复现实验和验证边界见 运行记录。
合成 relay 基准
benchmarks/run-relay.ps1 比较同一 JVM、同一个本机回声后端上的直连、原始字节 relay、按帧 relay,以及安装了实际 KeepAliveBridge 的按帧 relay。每个连接先预热,再重复发送固定长度的合成 Minecraft 帧并读取同样长度的回声;连接数、每连接消息数、预热数、帧负载字节数、重复轮数和同时在途的消息数均可配置。-Window 1 是逐包等待回声;更大的窗口使用独立写线程持续发送,读线程按顺序核对回声,并用信号量限制在途消息数。基准在轮次间交替执行三种 relay,并在首尾测直连基线。输出往返吞吐、按单向传输字节计算的 MiB/s、往返延迟 p50/p95/p99、JVM GC 次数/耗时和堆已用量变化。
.\benchmarks\run-relay.ps1 -Connections 8 -Messages 2000 -Warmup 200 -Payload 4096
.\benchmarks\run-relay.ps1 -Connections 8 -Messages 2000 -Warmup 200 -Payload 4096 -Window 16
该基准只覆盖本机 TCP 回声和三种 relay 数据路径。不同窗口使用不同的客户端发送方式,应在相同窗口内比较。负载是合成帧,不含登录、Forge 握手、模组流量、转服时的实体 ID 改写或真实客户端/后端行为;结果不代表 1.7.10 整合包等价性能,也不设 CI 性能门槛。堆变化是阶段前后的粗略观测,不是分配速率;请在目标机器、JDK 和连接规模上多轮运行并记录环境,避免把单次结果当成容量承诺。
本地一次测量的环境、参数与原始输出见 benchmarks/results/2026-09-25-local-relay.md。
加入实际 KeepAliveBridge 后的配对测量见 benchmarks/results/2026-09-25-keepalive-bridge.md;两份结果使用的 relay 事件循环安排不同,不能直接视为前后性能对比。
合成会话基准
benchmarks/run-proxy-session.ps1 在同一 JVM 中比较直接连接模拟后端,以及经过实际 ProxySessionListener 登录、落点选择和会话转发后连接同一后端。客户端先完成离线登录,读取 Join Game 和 Position and Look,再预热;计时仅包含固定 PLAY 帧的往返。-Window 设置每个客户端允许的在途请求数,默认 1(stop-and-wait);大于 1 时客户端最多连续发送 Window 个帧,再按 TCP 顺序读取回声并逐条校验,每收到一条便补发一条。预热和计时阶段、直连和代理都使用相同的发送算法。测得的单条延迟从该请求写出前计时到其回声读完;延迟样本和在途时间戳都使用有界数组,受 Connections、Messages 和 Window 上限约束。出现错误回声或阶段超时时基准以失败退出。
.\benchmarks\run-proxy-session.ps1 -Connections 4 -Messages 1000 -Warmup 100 -Payload 1024 -Repeats 2 -Window 1
.\benchmarks\run-proxy-session.ps1 -Connections 4 -Messages 1000 -Warmup 100 -Payload 1024 -Repeats 2 -Window 1 -Mode post-transfer
当前提交的多轮、同条件本机测量及原始输出见 生命周期修复后的会话基准记录。之前提交的测量见 原会话基准记录。这些是代理自身的合成流量性能验证,不是后端负载观测。
-Mode post-transfer 让代理客户端先从 bench 转到 replacement,直连客户端直接进入同一个 replacement 模拟后端;转服与预热均在计时外。两后端使用不同玩家实体 ID,因此代理在转服后仍运行实体 ID 映射路径。转服前后会话基准记录 包含窗口 1 和 16 的同条件多轮测量及原始输出。结果没有显示值得据此修改普通转发路径的稳定差异。
加入 Forge 握手阶段旧世界数据包过滤后,在当前代码上重跑的 转服后基准 与先前结果的范围重叠;合成流量没有显示稳定的大幅退化,目标整合包仍需单独测量。
同一提交在独立 Linux 容器中的 转服后会话基准 也完成了窗口 1 和 16 的各五轮对照与逐包校验。该结果仅验证另一运行环境中的合成路径;目标整合包和跨主机网络仍需实测。
当前会话路径的 JFR 采样记录 只提供了分配线索,代理 I/O 线程的 CPU 执行样本不足以定位热点;因此没有据此改动普通转发路径。
将 -Window 改为 16 可测每连接最多 16 个在途往返的情形。
原 stop-and-wait 小样本见 benchmarks/results/proxy-session-benchmark-smoke-2026-09-25.md;Window 参数的小样本和 Window=1/16 重复测量见 benchmarks/results/2026-09-25-proxy-session-window.md。这些是环回网络上的合成帧对照,不能代表 Forge 整合包、真实后端或跨主机部署的性能。