大厅与实例
实例表、端口、健康探测、占位与回收、预热池、票据与重连登记:大厅进程里的全部工作。
大厅只有一个职责:让每个对局都有一个活着、放得下人的房间进程。它自己跑
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。
| 配置 | 默认 | 作用 |
|---|---|---|
ProbeIntervalSeconds | 1.0 | 探测间隔 |
HealthFailureThreshold | 5 | 连续失败多少次判卡死 |
StartupTimeoutSeconds | 180.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 回来才算数:
| 配置 | 默认 | 作用 |
|---|---|---|
SetMatchAckTimeoutSeconds | 5.0 | 等 ACK 的超时 |
SetMatchAckRetryCount | 2 | 超时后重发次数 |
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 秒)会自退,避免孤儿实例。
下一页:房间与结算。