mirror of
https://github.com/layue13/MoonBridge.git
synced 2026-10-25 18:46:18 +00:00
Enforce single event loop for session backends
This commit is contained in:
1 parent
ffa9cffaff
commit
0bf460bfcf
2 files changed
+10
-1
No files matched your search
@@ -8,7 +8,7 @@ StrataProxy 是面向 Minecraft 1.7.10 Forge 的玩家会话代理。项目正
|
||||
- **服务器目录**:静态配置与插件调用同一个注册接口。目录维护地址、声明的容量,以及本代理连接和入服预留的数量;它不采集后端 CPU、TPS、内存等负载。同名重注册生成新句柄,旧异步结果不能修改新条目;同名同地址的旧连接仍占用容量,地址变更后旧连接只计入旧地址。容量预留同时确定本次连接使用的地址,避免动态更新夹在查询和预留之间时把会话计入错误后端。
|
||||
- **插件 API**:插件可查询 `PlayerView`、`ServerView`,动态注册后端,并以一个异步回调决定初始落点。空岛分配、实例唤醒和玩法数据由插件自行实现。单次选服失败或超时只结束当前请求,不停用插件;宿主关闭时会结束未决选服请求并撤销插件注册。插件返回的拒绝原因会作为登录断开消息发给玩家。
|
||||
- **发现方式**:DNS 是独立插件,核心不认识 DNS 或 Agent。配置只启用列出的插件;插件关闭时其注册会清理。普通 DNS 主机名通过独立解析器查询 A 与 AAAA 地址,不使用 JVM 的进程级地址缓存;`localhost` 按系统回环地址解析。最小默认选路按注册顺序选取后端,因此 DNS 插件先注册 IPv4 地址。一次查询失败会保留已有注册,连续三次失败会移除旧地址,恢复解析后重新注册。
|
||||
- **协议热路径**:会话始终保留协议 5 的帧边界,普通数据包不重新编码并尽量复用 Netty `ByteBuf`;Keep Alive ID 在会话内转换,可同时跟踪多个未回复请求,转服后旧后端的迟到回复会被丢弃;转服后只改写少数与玩家实体 ID 相关的包,并按目标通道可写状态控制读取。后端主机名由 Netty 异步解析,避免把 DNS 等待放在玩家 I/O 线程上。
|
||||
- **协议热路径**:同一玩家的前端和后端通道绑定同一个 Netty 事件循环,会话控制状态在该循环上串行更新。会话始终保留协议 5 的帧边界,普通数据包不重新编码并尽量复用 Netty `ByteBuf`;Keep Alive ID 在会话内转换,可同时跟踪多个未回复请求,转服后旧后端的迟到回复会被丢弃;转服后只改写少数与玩家实体 ID 相关的包,并按目标通道可写状态控制读取。后端主机名由 Netty 异步解析,避免把 DNS 等待放在玩家 I/O 线程上。
|
||||
- **日志**:SLF4J API 与 Logback 运行时。
|
||||
|
||||
## 当前可运行范围
|
||||
|
||||
@@ -53,6 +53,7 @@ import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Mutable session control state is confined to the frontend event loop. */
|
||||
@io.netty.channel.ChannelHandler.Sharable
|
||||
final class Session extends ChannelInboundHandlerAdapter {
|
||||
private static final int MAX_TRANSITION_BUFFER_BYTES = ProtocolProfile.minecraft1710().maxFrameBytes();
|
||||
@@ -316,6 +317,7 @@ final class Session extends ChannelInboundHandlerAdapter {
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
.handler(new io.netty.channel.ChannelInitializer<SocketChannel>() {
|
||||
@Override protected void initChannel(SocketChannel channel) {
|
||||
requireSessionEventLoop(channel);
|
||||
installCodecs(channel.pipeline());
|
||||
channel.pipeline().addLast("initial-session", Session.this);
|
||||
}
|
||||
@@ -631,6 +633,7 @@ final class Session extends ChannelInboundHandlerAdapter {
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
.handler(new io.netty.channel.ChannelInitializer<SocketChannel>() {
|
||||
@Override protected void initChannel(SocketChannel channel) {
|
||||
requireSessionEventLoop(channel);
|
||||
channel.pipeline().addLast("minecraft-frame-decoder",
|
||||
new dev.strataproxy.core.protocol.MinecraftFrameDecoder(
|
||||
ProtocolProfile.minecraft1710(), true));
|
||||
@@ -680,6 +683,12 @@ final class Session extends ChannelInboundHandlerAdapter {
|
||||
return InetSocketAddress.createUnresolved(host, address.getPort());
|
||||
}
|
||||
|
||||
private void requireSessionEventLoop(Channel channel) {
|
||||
if (channel.eventLoop() != frontend.eventLoop()) {
|
||||
throw new IllegalStateException("backend channel must share the player session event loop");
|
||||
}
|
||||
}
|
||||
|
||||
private void candidateReady(TransferAttempt attempt) {
|
||||
if (transfer != attempt || attempt.finished || closed.get()) return;
|
||||
attempt.cutoverDeadline = frontend.eventLoop().schedule(() -> {
|
||||
|
||||
Reference in new issue
Block a user