Skip to Content
CrossingNet
大厅与实例

大厅与实例

实例表、端口、健康探测、占位与回收、预热池、票据与重连登记:大厅进程里的全部工作。

大厅只有一个职责:让每个对局都有一个活着、放得下人的房间进程。它自己跑 UCrossingSessionSubsystem,围绕一张实例表做四件事:拉起、盯着、分配、回收。

实例表

每条记录至少包含:InstanceId、Port、HealthPort、状态、当前人数、容量、matchId、健康计数、 创建时间、关闭原因。查询入口:

TArray<FCrossingInstanceRecord> All = Session->GetInstances();

FCrossingInstanceRecord One;
if (Session->GetInstance(Id, One)) { /* One.State / One.Port / One.CloseReason */ }

Closed 记录保留 ClosedRetentionSeconds(默认 60 秒),方便结算回传时还能查到。

端口

两段独立的端口,各自 bind 实测:

GamePortRange=(X=57000,Y=57100)     ; UDP,房间游戏端口
HealthPortRange=(X=58100,Y=58150)   ; TCP,健康探测
LobbyControlPort=57300              ; TCP,大厅自己

拉起实例时先占住一对端口;进程真起来之前不会把端口再分给别人,启动失败会释放。

拉起一个实例

FGuid InstanceId;
int32 Port = 0;
FString Error;

const bool bOk = Session->SpawnInstance(
    /*Mode=*/TEXT("2v2"),
    InstanceId, Port, Error,
    /*MaxPlayers=*/4,
    /*BotCount=*/0,
    /*Teams=*/2,
    /*bWarm=*/false);

后四个参数可选:容量、Bot 数、队伍数、是否作为预热实例。bWarm=true 时不分配给人, 起来后进预热池等人认领。

拉起过程写这些日志:

[Session] event=SpawnStart mode=2v2 port=57000
[Session] event=HealthFirstOK port=57000
[Session] event=InstanceReadyAgain port=57000     (回收后又可用)

健康探测

大厅每 ProbeIntervalSeconds(默认 1 秒)用 TCP 连一次健康端口,房间回一行:

OK;<players>;<state>;<uptimeSeconds>

例:OK;3;Playing;1240。

配置默认作用
ProbeIntervalSeconds1.0探测间隔
HealthFailureThreshold5连续失败多少次判卡死
StartupTimeoutSeconds180.0拉起后多久没健康算启动失败

连续失败达到阈值 → 实例标 Closed(Unresponsive),并尝试让房间优雅退出。

占位

分配给人之前先加锁,避免两个匹配组同时看到「还剩 1 个位置」:

Session->ReserveSlot(InstanceId, +1);   // 占位
Session->ReserveSlot(InstanceId, -1);   // 释放
int32 Left = Session->GetAvailableSlots(Record);

占位有超时:ReservationTimeoutSeconds(默认 60 秒)没转成真实玩家就自动释放。

票据

大厅是唯一能签发票据的地方:

FString Token = Session->CreateJoinToken(
    InstanceId, PlayerId, ECrossingJoinRole::Player, /*TeamId=*/0);

票据是无状态签名,房间进程用同一个 JoinTokenSecret 校验,不依赖大厅在线。 格式与安全边界见协议。

预热池

bEnableWarmPool=False     ; 默认关
WarmPoolMode=1v1
WarmPoolSize=1
WarmPoolCooldownSeconds=15.0

池里常备 Ready 的空实例。匹配成立时:

Session->ClaimWarmInstance(Mode, Capacity, MatchId, OutId, OutPort);
Session->AssignMatchToInstance(InstanceId, MatchId);   // 写 matchId

写入 matchId 走控制通道的 SETMATCH,要等 CMD_ACK 回来才算数:

配置默认作用
SetMatchAckTimeoutSeconds5.0等 ACK 的超时
SetMatchAckRetryCount2超时后重发次数

ACK 没回来就不把玩家登记进这一局,避免「房间以为自己是另一场」的错配。这一条也适用于 回收后重新可用的实例(InstanceReadyAgain)。

重连登记

房间在玩家掉线时把座位保留一段时间,并推一条 DISCONNECT 给大厅;大厅记进重连表:

Session->RegisterReconnect(PlayerId, InstanceId, ExpiresAt);
Session->PeekReconnect(PlayerId, OutInstanceId);     // 不消费
Session->ConsumeReconnect(PlayerId, OutInstanceId);  // 消费一次
Session->ClearReconnect(PlayerId);

ConsumeReconnect 成功后签发的票据角色是 ReconnectPlayer,旅行回去时房间用 TryReclaimSeat 找回原座位。超过 ReconnectWindowSeconds(默认 120 秒)窗口,登记失效。

单会话

同一账号同时只允许一个会话:

bEnableSingleSession=True

大厅在玩家登录时登记;后到的会话会把前一个标成 SessionReplaced 并断开。 房间侧也会做一次同名检查(RoomSessionDuplicate)。

指标

每 MetricsIntervalSeconds(默认 10 秒)汇总一次:

FString Json        = Session->GetMetricsJson();
FString Prometheus  = Session->GetMetricsPrometheus();

看的数:按状态的实例数、队列人数、平均等待、结算重试次数、控制通道超时次数。 日志里对应 [Session] event=MetricsSnapshot。

回收

机制配置行为
空置回收bEnableEmptyReap、EmptyGraceSeconds(30)Ready 且从没进过人,宽限期后关掉
启动超时StartupTimeoutSeconds(180)Spawning 太久没健康,关掉并记原因
预留超时ReservationTimeoutSeconds(60)占位超时自动释放
记录保留ClosedRetentionSeconds(60)Closed 记录留一段时间
上限MaxInstances(8)到上限后匹配排队等实例

每次回收都写一条带原因的记录,CrossingNet.Status 与 SettlementMissing 都能对上。

与房间的心跳

房间会定期向大厅报健康(LobbyHeartbeat),大厅反过来也能推 STOP 让房间优雅退出。 房间发现大厅长时间不在(LobbyLostTimeoutSeconds,默认 60 秒)会自退,避免孤儿实例。

下一页:房间与结算。

On this page