FMassBattleSnapshotTcpTransport
Source/MassBattle/Public/Network/MassBattleSnapshotTcpTransport.h
概述 Overview
FEntitySnapshotPayload 传输的原始 TCP 侧通道(纯 C++ 类,非 UObject),替代基于 RPC 的快照发送以避免 DataChannel !IsBunchTooLarge 限制。应用层帧协议:每帧 = uint32 大小前缀 + 二进制头部(FMassBattleSnapshotTcpHeader,Magic "MBTS" + Version 1)+ 载荷;1 MiB 分块传输、收端重组(TBitArray 去重)、CRC 校验、zlib 压缩。服务器监听 + 多客户端认证(令牌 + 玩家槽位),客户端非阻塞连接;另承载 lockstep 大负载自动分流(TCP 数据块)的上下行传输。可靠性设计:发送有界重试(EWOULDBLOCK 100 次 ≈ 1s)、应用层心跳(15s)+ 对端超时(60s)检测半开连接、恶意头大小上限(分配前校验)、挂起认证连接上限(16,抗连接洪泛)、死 socket 自愈闭环(发送失败 → 挂起 → 客户端重连 → 认证后补发)、下行块认证前补发(每次广播暂存,新认证槽位冲刷)。另内置调试用网络模拟(进程级:延迟/抖动/丢帧/限速,仅影响数据帧,控制帧直发)。
Raw TCP side-channel for FEntitySnapshotPayload transfer (plain C++ class, not a UObject), replacing RPC-based snapshot delivery to avoid the DataChannel !IsBunchTooLarge limit. Application-layer frame protocol: each frame = uint32 size prefix + binary header (FMassBattleSnapshotTcpHeader, Magic "MBTS" + Version 1) + payload; 1 MiB chunked transfer, receiver-side reassembly (TBitArray dedup), CRC validation, zlib compression. The server listens and authenticates multiple clients (token + player slot); clients connect non-blocking. It also carries the lockstep auto-flood (TCP data block) traffic in both directions. Reliability design: bounded send retries (100 × EWOULDBLOCK ≈ 1s), application-layer heartbeats (15s) plus a peer timeout (60s) for half-open detection, hostile-header size caps (validated before allocation), a pending-auth connection cap (16, against connection floods), a dead-socket self-healing loop (send failure → hang → client reconnect → auth → flush), and pre-auth downlink-block backfill (every broadcast parks a copy; newly authenticated slots get it flushed). It also embeds debug network emulation (process-global latency/jitter/drop/bandwidth; data frames only — control frames bypass).
常量 Constants
| 常量 Constant | 值 Value | 说明 Description |
|---|---|---|
MB_SNAPSHOT_TCP_CHUNK_SIZE | 1 MiB | 每个 TCP 帧负载的最大字节数(应用层分块)。Maximum bytes per TCP frame payload (app-level chunk). |
MB_SNAPSHOT_TCP_MAX_SNAPSHOT_BYTES | 256 MB | 重组接受的最大声明压缩大小——限制恶意头字段导致的分配。Maximum declared compressed size accepted for reassembly — bounds allocations from hostile header values. |
MB_SNAPSHOT_TCP_MAX_UNCOMPRESSED_BYTES | 512 MB | 解压接受的最大声明未压缩大小。Maximum declared uncompressed size accepted for decompression. |
MB_SNAPSHOT_TCP_MAX_TRANSFERS | 4 | 并行入站传输的最大数量(快照与下行 lockstep 各一组)。Maximum concurrent inbound transfers (one set each for snapshots and downlink lockstep). |
MB_SNAPSHOT_TCP_MAX_UPLOAD_TRANSFERS | 32 | 并发上行重组条目上限——发送方是不可信客户端,无数量上限时每个 (slot, BlockId) 一帧即可无限增殖 UploadReassemblyStates;32 远超合法用量(客户端同时只有 1-2 条大命令在途)。Cap on concurrent uplink reassembly entries — the sender is an untrusted client; without a count bound, one frame per (slot, BlockId) grows UploadReassemblyStates forever. 32 is far beyond legit use (a client has 1-2 large commands in flight). |
MB_SNAPSHOT_TCP_MAX_FRAME | CHUNK + 256 | TCP 帧的最大大小(头部 + 最大块)。Maximum TCP frame size (header + max chunk). |
MB_SNAPSHOT_TCP_SENDFRAME_RETRY_LIMIT | 100 | SendFrame 在 EWOULDBLOCK 下的最大重试次数(10ms × 100 ≈ 1s)——把阻塞式发送从"无限"收敛为"有界"。Max SendFrame EWOULDBLOCK retries (~1s @ 10ms) — bounds a wedged send instead of blocking forever. |
MB_SNAPSHOT_TCP_CLIENT_CONNECT_TIMEOUT_SECONDS | 5.0 | 客户端连接/认证截止——EINPROGRESS 悬挂超时即销毁并重试。Client connect/auth deadline — a connect stuck in EINPROGRESS beyond this is torn down and re-attempted. |
MB_SNAPSHOT_TCP_CLIENT_RECONNECT_THROTTLE_SECONDS | 1.0 | 客户端自动重连的节流间隔。Throttle between automatic client reconnects. |
MB_SNAPSHOT_TCP_AUTH_TIMEOUT_SECONDS | 10.0 | 服务器侧认证超时——窗口内未发送 ClientAuth 的 socket 被销毁。Server-side auth timeout — a socket that never sends ClientAuth within this window is destroyed. |
MB_SNAPSHOT_TCP_PENDING_SNAPSHOT_TIMEOUT_SECONDS | 30.0 | 挂起出站快照过期(与重组超时一致)。Pending outbound snapshot expiry (matches the reassembly timeout). |
MB_SNAPSHOT_TCP_LOCKSTEP_REASSEMBLY_TIMEOUT_SECONDS | 30.0 | lockstep 下行重组超时。Lockstep downlink reassembly timeout. |
MB_SNAPSHOT_TCP_MAX_PENDING_AUTH | 16 | 服务器侧同时挂起(未认证)连接的上限——限制裸连接洪泛的 FD 与映射增长(审计 H5);已认证 socket 数量本就受 ExpectedPlayerSlots 约束。超出的连接先 accept 再立即关闭(排空内核 accept 队列,合法对端不会因积压满而停顿);拒绝日志每秒至多一条。Server-side cap on simultaneously PENDING (not yet authenticated) connections — bounds FD and map growth from raw connection floods (audit H5); authenticated sockets are already bounded by ExpectedPlayerSlots. Excess connections are accepted then immediately closed (drains the kernel accept queue so legit peers never stall on a full backlog); rejection logs are throttled to at most one per second. |
MB_SNAPSHOT_TCP_HEARTBEAT_INTERVAL_SECONDS | 15.0 | 心跳发送间隔。UE 5.6 FSocket 不暴露 keepalive,recv 探测只能检测 FIN/RST——半开连接(对端崩溃无 RST / NAT 静默丢弃)会永久"已连接"并使 resync 通道死锁。Heartbeat send interval. UE 5.6 FSocket exposes no keepalive and the recv probe only detects FIN/RST — a half-open connection (peer crash without RST / silent NAT drop) would stay "connected" forever and deadlock the resync channel. |
MB_SNAPSHOT_TCP_PEER_TIMEOUT_SECONDS | 60.0 | 对端超时——在此时长内未收到对端任何类型的帧即拆除连接。Peer timeout — the connection is torn down when no frame of ANY type arrives from the peer within this window. |
EMassBattleSnapshotTcpMsgType
帧消息类型(普通 enum class,非 UENUM)。注意 MessageType 0 被保留为服务器认证拒绝信号(SendAuthFailure):拒绝帧不携带 token——客户端仅凭此类型识别,检查先于其 token 门槛(审计 C2:绝不向被拒的未认证对端回传会话 token)。
Frame message types (plain enum class, not UENUM). Note that MessageType 0 is reserved as the server auth-rejection signal (SendAuthFailure): the rejection frame carries NO token — the client identifies it by this type alone, BEFORE its token gate (audit C2: never echo the session token to a rejected, unauthenticated peer).
| 值 Value | 说明 Description |
|---|---|
ClientAuth = 1 | 客户端→服务器:用 token+PlayerSlot 认证。client→server: authenticate with token+PlayerSlot. |
SnapshotChunk = 2 | 服务器→客户端:多块快照传输中的单个块。server→client: one chunk of a multi-chunk snapshot transfer. |
LockstepDataChunk = 3 | 服务器→客户端:大 lockstep 负载块的单个分片(下行自动分流)。server→client: one chunk of a large lockstep-payload block (downlink auto-flood). |
LockstepUploadChunk = 4 | 客户端→服务器:大 lockstep 负载块的单个分片(上行自动分流)。client→server: one chunk of a large lockstep-payload block (uplink auto-flood). |
Heartbeat = 5 | 双向:半开连接活性探测。both directions: liveness probe for half-open connections. |
FMassBattleSnapshotTcpHeader
每个 TCP 帧内的二进制头部,线路上由 uint32 帧大小前缀引导。SnapshotId 在 lockstep 块路径被复用为 BlockId,TargetTick 复用为 ExecuteTick。
Binary header inside every TCP frame, preceded by a uint32 frame-size prefix on the wire. SnapshotId is reused as BlockId on the lockstep-block path; TargetTick is reused as ExecuteTick.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Magic | uint32 | 0x4D425453 | 魔数 "MBTS";校验失败即丢弃帧。Magic "MBTS"; frames failing the check are dropped. |
Version | uint16 | 1 | 协议版本。Protocol version. |
MessageType | uint16 | 0 | 消息类型(EMassBattleSnapshotTcpMsgType;0 = 认证拒绝)。Message type (EMassBattleSnapshotTcpMsgType; 0 = auth rejection). |
SnapshotId | int32 | 0 | 每次传输的唯一 ID(lockstep 路径复用为 BlockId)。Unique per-transfer ID (reused as BlockId on the lockstep path). |
PlayerSlot | int32 | -1 | 目标玩家槽位。Target player slot. |
PayloadTick | int32 | 0 | 快照捕获时的 tick。Tick when the snapshot was captured. |
TargetTick | int32 | 0 | 应用快照的目标 tick(lockstep 路径复用为 ExecuteTick)。Tick to apply the snapshot at (reused as ExecuteTick on the lockstep path). |
Token | int32 | 0 | 会话认证令牌。Session auth token. |
ChunkIndex | int32 | 0 | 从 0 开始的块索引。0-based chunk index. |
ChunkCount | int32 | 0 | 总块数。Total chunks. |
TotalCompressedSize | int32 | 0 | 组装后的压缩字节数(分配前校验上限)。Assembled compressed byte count (capped before allocation). |
TotalUncompressedSize | int32 | 0 | 解压后的字节数。Decompressed byte count. |
ChunkPayloadSize | int32 | 0 | 本帧头之后的字节数。Bytes following this header in this frame. |
Crc | uint32 | 0 | 组装后压缩数据的 CRC32。CRC32 of the assembled compressed blob. |
FMassBattleTcpReassembly
入站传输重组状态:记录元数据、按块位图去重、累计已收字节。供快照、下行 lockstep 与服务器上行三类重组复用。
Inbound transfer reassembly state: records metadata, dedups via a chunk bitmap, and accumulates received bytes. Shared by snapshot, downlink lockstep, and server-side uplink reassembly.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
SnapshotId | int32 | 0 | 传输 ID(lockstep 路径为 BlockId)。Transfer ID (BlockId on the lockstep path). |
PayloadTick / TargetTick | int32 ×2 | 0 | 捕获 tick / 应用(执行)tick,首块初始化时记录。Capture / apply (execute) tick, recorded at first-chunk init. |
ChunkCount / TotalCompressedSize / TotalUncompressedSize | int32 ×3 | 0 | 元数据,首块初始化;后续块须与之一致(不一致取消)。Metadata, initialized on the first chunk; later chunks must match (mismatch cancels). |
ExpectedCrc | uint32 | 0 | 预期 CRC。Expected CRC. |
BytesReceived | int32 | 0 | 累计已收字节。Accumulated received bytes. |
CompressedBuffer | TArray<uint8> | 空 | 重组缓冲。Reassembly buffer. |
ReceivedChunks | TBitArray<> | 空 | 块到位位图(去重)。Per-chunk received bitmap (dedup). |
FirstChunkTime | double | 0.0 | 首块墙钟时间(超时清扫锚点)。First-chunk wall-clock time (timeout-sweep anchor). |
bActive | bool | false | 槽位是否被占用。Whether the slot is occupied. |
方法 Methods
把全部字段复位到默认值并释放缓冲。
Resets all fields to defaults and releases buffers.
Input
无参数。
No parameters.
Output
无返回值。副作用:全部成员复位。
No return value. Side effect: all members reset.
传输是否完成:占用中且位图置位数等于 ChunkCount。
Whether the transfer is complete: active and set-bit count equals ChunkCount.
Input
无参数。
No parameters.
Output
返回是否完成。
Returns whether complete.
委托 Delegates
| 委托 Delegate | 签名 Signature | 说明 Description |
|---|---|---|
OnSnapshotBytesReceived | FOnTcpSnapshotBytesReceived (int32 SnapshotId, int32 PayloadTick, int32 TargetTick, const TArray<uint8>& UncompressedBytes) | 客户端:完整快照接收、重组、解压、CRC 校验通过后触发。Fired when a complete snapshot is received, reassembled, decompressed, and CRC-validated. |
OnLockstepDataBlockReceived | FOnLockstepDataBlockReceived (int32 BlockId, int32 ExecuteTick, const TArray<uint8>& DecompressedBytes) | 客户端:完整下行 lockstep 块重组、解压、CRC 校验通过后触发(自动分流)。Fired on the client when a complete downlink lockstep data block is validated (auto-flood). |
OnLockstepDataBlockUploaded | FOnLockstepDataBlockUploaded (int32 PlayerSlot, int32 BlockId, int32 ExecuteTick, const TArray<uint8>& DecompressedBytes) | 服务器:完整上行 lockstep 块重组、解压、CRC 校验通过后触发。Fired on the server when a complete uplink lockstep data block is validated. |
方法 Methods
在指定端口启动 TCP 监听(权威端调用)。非阻塞、可复用、监听队列 8、收发缓冲各 1 MiB。Port=0 时 OS 分配空闲端口并读回实际端口(GetServerPort 返回真实值供客户端连接)。已在运行时返回 false。由 UMassBattleNetworkSubsystem::InitializeSnapshotTcpTransport 调用(游戏端口 +100~+109 范围尝试,失败回退 Port=0)。
Starts the TCP listener on the given port (call on the authority). Non-blocking, reusable, backlog 8, 1 MiB receive/send buffers. With Port=0 the OS assigns a free port which is read back (GetServerPort returns the real value for clients). Returns false when already running. Called by UMassBattleNetworkSubsystem::InitializeSnapshotTcpTransport (tries game port +100~+109, falls back to Port=0).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Port | int32 | 监听端口;0 = OS 分配。Listen port; 0 = OS-assigned. |
InSessionToken | int32 | 会话认证令牌。Session auth token. |
BindAddress | const FString& | 绑定地址,默认 127.0.0.1;解析失败回退 Any。Bind address, default 127.0.0.1; parse failure falls back to Any. |
Output
返回是否启动成功。副作用:创建监听 socket、记录端口与令牌。
Returns whether the server started. Side effects: listen socket creation, port/token recording.
注册玩家槽位,让服务器知道该期待该连接——仅将槽位加入 HandleClientAuth 白名单,不排队快照。由 AMassBattleNetworkGameModeBase::PostLogin 的 TCP 预连接握手调用:使客户端在首个大载荷 lockstep 命令(自动分流)或 resync 真正需要 TCP socket 之前就先建好连接。
Registers a player slot so the server knows to expect it — whitelists the slot for HandleClientAuth WITHOUT queueing a snapshot. Called by the PostLogin pre-connect handshake in AMassBattleNetworkGameModeBase so a client establishes its TCP socket before the first large-payload lockstep command (auto-flood) or resync actually needs it.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
PlayerSlot | int32 | 要预注册的玩家槽位。Player slot to pre-register. |
Output
返回是否注册成功;槽位为负或已释放(FreedPlayerSlots,玩家已登出——非合法认证目标)时返回 false。副作用:槽位加入预期认证集合。
Returns whether the registration succeeded; false for negative slots or freed ones (FreedPlayerSlots, player logged out — not a legitimate auth target). Side effect: the slot joins the expected-auth set.
网络模拟设置(调试):进程级全局——本进程内所有传输实例(PIE 同时承载服务器 + 客户端传输)在发送时读取,一次调用覆盖全部出站 TCP 数据帧。包含 Latency/Jitter(到期队列,Tick 内按每 socket FIFO 冲刷)、DropPercent(帧级概率丢弃——压力测试缺块 → desync → resync 恢复链)、BandwidthLimitKBs(令牌桶限速)。丢失事件在冲刷时掷出;语义按 EMassBattleNetLossMode:RetransmitLate = 丢帧等待 RetransmitDelayMs 后重发,对其 socket 造成队头阻塞(真实的 Reliable/TCP——迟到而非丢失);PermanentDiscard = 永不到达(desync 恢复压力测试)。控制帧(认证/心跳/认证拒绝)永远直发——握手时序保持真实。TCP 刻意不提供重复/乱序旋钮:TCP 设计上就去重且保序,这类效果请用 UE-UDP 路径旋钮。镜像 cvar(仅 TCP 路径):MassBattle.NetEmulation.*;FuncLib 的 BP 封装(SetNetworkEmulation)同时设置 UE 的 net.PktLag 系列,两条传输一起受影响。
Network-emulation settings (debug): process-global — every transport instance in this process (PIE hosts server + client transports) reads them at send time, so one call covers all outbound TCP data frames. Applies Latency/Jitter (due-time queue, per-socket FIFO flush in Tick), DropPercent (frame-level probabilistic drop — stresses missing-block → desync → resync recovery), and BandwidthLimitKBs (token-bucket throttle). Loss events roll at flush time; semantics per EMassBattleNetLossMode: RetransmitLate = a lost frame waits RetransmitDelayMs then retries, head-of-line blocking its socket (realistic Reliable/TCP — late, not lost); PermanentDiscard = never arrives (desync-recovery stress). Control frames (auth / heartbeat / auth-reject) always bypass — handshake timing stays real. TCP deliberately has NO duplicate/reorder knobs: TCP dedupes and preserves order by design; use the UE-UDP path knobs for those effects. Mirror cvars (TCP path only): MassBattle.NetEmulation.*; the FuncLib BP wrapper (SetNetworkEmulation) also sets UE's net.PktLag family so both transports are impaired together.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Settings | const FMassBattleNetEmulationSettings& | 模拟参数(Latency/Jitter/Drop/LossMode/Retransmit/Bandwidth/Seed;仅 SetEmulationSettings)。Emulation parameters (Latency/Jitter/Drop/LossMode/Retransmit/Bandwidth/Seed; SetEmulationSettings only). |
Output
Get 返回当前设置快照。副作用:Set 覆写全部进程级模拟状态并重置随机流/令牌桶(RandomSeed=0 时以墙钟重新播种)。
Get returns a snapshot of the current settings. Side effect: Set overwrites all process-global emulation state and reseeds the RNG / token bucket (a RandomSeed of 0 reseeds from wall clock).
作为客户端连接指定 host:port(远程客户端调用)。残留死 socket(SCS_NotConnected/SCS_ConnectionError)先销毁再建新连接;非阻塞 connect,EINPROGRESS 为预期。保存端点供 Tick 节流自动重连,设置 5s 连接/认证截止。已在连接中返回 false。
Connects as a client to the given host:port (call on remote clients). Stale sockets (SCS_NotConnected/SCS_ConnectionError) are destroyed before establishing a fresh connection; non-blocking connect with EINPROGRESS expected. The endpoint is saved for Tick's throttled auto-reconnect and a 5s connect/auth deadline is set. Returns false when already connected.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Host | const FString& | 服务器地址。Server address. |
Port | int32 | 服务器端口。Server port. |
InPlayerSlot | int32 | 本客户端玩家槽位(认证用)。This client's player slot (auth). |
InSessionToken | int32 | 会话令牌。Session token. |
Output
返回连接是否发起成功(非阻塞,实际连接状态后续由 Tick 状态机推进)。
Returns whether the connect was initiated (non-blocking; the actual state advances via the Tick state machine).
关闭并释放全部 socket:客户端、已认证玩家、待认证(销毁而非仅丢弃——堵住 FD 泄漏)、监听;清空全部状态与重组槽。析构函数调用。
Shuts down and releases all sockets: client, authenticated players, pending-auth (destroyed, not just dropped — plugs FD leaks), and the listener; clears all state and reassembly slots. Called by the destructor.
Input
无参数。
No parameters.
Output
无返回值。副作用:销毁全部 socket 并清空状态。
No return value. Side effects: all sockets destroyed, state cleared.
释放玩家槽位持有的全部 transport 资源(由 GameMode::FreePlayerSlot 调用):移除预期认证注册、槽位进入 FreedPlayerSlots、移除挂起快照、清理该槽位 socket。迟到的 SendSnapshot 对已释放槽位变为无操作而不是重新注册。
Releases all transport resources owned by a player slot (called by GameMode::FreePlayerSlot): removes the expected-auth registration, adds the slot to FreedPlayerSlots, removes pending snapshots, and cleans up the slot's socket. Late SendSnapshot calls no-op for freed slots instead of re-registering them.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
PlayerSlot | int32 | 要释放的槽位。Slot to release. |
Output
无返回值。副作用:注册表/队列/socket 清理。
No return value. Side effects: registry/queue/socket cleanup.
重新注册被重新分配给新玩家的槽位(由 GameMode::AllocatePlayerSlot 调用):清除 FreedPlayerSlots 守卫,使 SendSnapshot 重新加入预期认证注册、认证得以再次通过。
Re-registers a slot re-allocated to a new player (called by GameMode::AllocatePlayerSlot): clears the FreedPlayerSlots guard so SendSnapshot re-adds the slot to expected auth and auth can succeed again.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
PlayerSlot | int32 | 要复用的槽位。Slot to reuse. |
Output
无返回值。副作用:清除 freed 守卫。
No return value. Side effect: freed guard cleared.
服务 tick(由 UMassBattleNetworkSubsystem::TickSnapshotTcpTransport 调用):服务器侧——accept 新连接(挂起认证上限 16,超出先 accept 再立即关闭,审计 H5)、轮询已认证与待认证 socket(drain 数据、recv 探测 FIN/RST、认证超时 10s、心跳广播 15s、半开检测 60s)、驱逐过期挂起快照(30s);客户端侧——认证拒绝之外的节流自动重连(1s)、连接状态机(连接错误/超截止/已连接:认证发送、drain 全部数据、探测、心跳、对端超时)、驱逐过期重组槽(快照 30s / lockstep 30s / 上行 30s)。网络模拟开启时,本 tick 还按每 socket FIFO 冲刷到期延迟帧并执行令牌桶限速。
Service tick (called by UMassBattleNetworkSubsystem::TickSnapshotTcpTransport): server side — accepts connections (pending-auth cap 16; excess accepted then immediately closed, audit H5), polls authenticated and pending-auth sockets (drain, recv FIN/RST probe, 10s auth timeout, 15s heartbeat broadcast, 60s half-open detection), evicts stale pending snapshots (30s); client side — throttled auto-reconnect except after auth rejection (1s), the connection state machine (connection error / deadline / connected: auth send, full drain, probe, heartbeat, peer timeout), and eviction of stale reassembly slots (30s each for snapshot / lockstep / uplink). When network emulation is active, this tick also flushes due delayed frames per socket (FIFO) and applies the token-bucket bandwidth limit.
Input
无参数。
No parameters.
Output
无返回值。副作用:收发帧、回调触发、死 socket 清理、超时驱逐。
No return value. Side effects: frame I/O, callback firing, dead-socket cleanup, timeout eviction.
向指定玩家发送或排队 snapshot 负载,内部自动分块。槽位已释放则 no-op(返回新 SnapshotId,不重新注册);socket 未认证时挂起队列等待认证后 FlushPendingSnapshotForPlayer 补发。发送失败:清理死 socket + 挂起快照并返回成功——RPC 照发,客户端重连认证替换 socket 后补发,自愈闭环。挂起用 latest-wins(乱序异步派发回调不得覆盖更新的排队快照)。
Sends or queues a snapshot payload to a specific player, chunking internally. A freed slot no-ops (returns the new SnapshotId without re-registering); an unauthenticated socket queues the snapshot until auth completes and FlushPendingSnapshotForPlayer delivers it. On send failure: the dead socket is cleaned up, the snapshot hung, and success is still returned — the RPC goes out anyway, the client reconnects, auth replaces the socket, and the snapshot is delivered: a self-healing loop. Pending uses latest-wins (out-of-order async dispatch callbacks never overwrite a newer queued snapshot).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
PlayerSlot | int32 | 目标玩家槽位。Target player slot. |
PayloadTick | int32 | 快照捕获 tick。Snapshot capture tick. |
TargetTick | int32 | 应用目标 tick。Apply target tick. |
CompressedPayload | const TArray<uint8>& | 已压缩负载。Compressed payload. |
UncompressedSize | int32 | 解压字节数(头部元数据)。Uncompressed byte count (header metadata). |
Crc | uint32 | 压缩数据 CRC。Compressed-data CRC. |
Output
返回 SnapshotId(正数)——注意:发送失败也返回正数(自愈闭环设计);槽位无效时返回 -1。副作用:注册预期认证、挂起或分块发送。
Returns the SnapshotId (positive) — note: send failures also return positive (the self-healing design); -1 for invalid slots. Side effects: expected-auth registration, hang or chunked send.
向所有已认证客户端广播大 lockstep 负载块(下行自动分流)。每次广播都暂存一份副本(FIFO,上限 16 条,最旧淘汰)——后认证的客户端从未见过该块,而引用命令(引擎复制)仍会到达:没有补发时加入端在 ExecuteTick 冻结 10 秒等一个从未发送的块,随后落入 resync 循环(beginplay 的 FF.RebuildFlowField 早于客户端 TCP 认证数秒是典型案例);每个新认证槽位都会冲刷收到的暂存块。BlockId 为权威 id;ExecuteTick 为匹配引用命令的触发 tick。无已认证客户端返回 false(暂存副本在认证时补给加入端);发送失败的 socket 清理(下次广播不再命中)。
Broadcasts a large lockstep-payload block to ALL authenticated clients (downlink auto-flood). Every broadcast parks a copy (FIFO, capped at 16, oldest evicted) — a client authenticating later never saw it, while the reference command (engine replication) still reaches it: without the backfill the joiner froze 10s at ExecuteTick waiting for a block that was never sent, then fell into the resync loop (beginplay-time FF.RebuildFlowField firing seconds before the client's TCP auth is the canonical case); every newly authenticated slot gets the parked blocks flushed to it. BlockId is the authoritative id; ExecuteTick is when the matching reference command fires. Returns false with no authenticated clients (the parked copy serves the joiner at auth); failed sockets are cleaned up (so the next broadcast skips them).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
BlockId | int32 | 权威块 ID。Authoritative block ID. |
ExecuteTick | int32 | 引用命令的执行 tick。Execute tick of the reference command. |
CompressedData | const TArray<uint8>& | 压缩负载。Compressed payload. |
UncompressedSize | int32 | 解压字节数。Uncompressed byte count. |
Crc | uint32 | 压缩数据 CRC。Compressed-data CRC. |
Output
返回是否至少向一个客户端发出。副作用:分块发送、死 socket 清理。
Returns whether at least one client received the send. Side effects: chunked sends, dead-socket cleanup.
客户端向服务器上传大 lockstep 负载块(上行自动分流)。BlockId 为本地分配(AllocLocalBlockId),服务器汇聚时重映射为权威 ServerBlockId。
Uploads a large lockstep-payload block from client to server (uplink auto-flood). BlockId is locally allocated (AllocLocalBlockId); the server remaps it to the authoritative ServerBlockId on convergence.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
BlockId | int32 | 本地块 ID。Local block ID. |
ExecuteTick | int32 | 执行 tick(上行时通常为发送方当前 tick)。Execute tick (usually the sender's current tick on uplink). |
CompressedData | const TArray<uint8>& | 压缩负载。Compressed payload. |
UncompressedSize | int32 | 解压字节数。Uncompressed byte count. |
Crc | uint32 | 压缩数据 CRC。Compressed-data CRC. |
Output
返回是否发出(客户端 socket 缺失时 false)。
Returns whether the send was issued (false without a client socket).
zlib 压缩(FArchiveSaveCompressedProxy, NAME_Zlib);空输入返回空数组;压缩错误返回空数组。
zlib compression (FArchiveSaveCompressedProxy, NAME_Zlib); empty input returns an empty array; compression errors return an empty array.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
UncompressedData | const TArray<uint8>& | 未压缩字节。Uncompressed bytes. |
Output
返回压缩字节;失败返回空数组。
Returns compressed bytes; empty on failure.
zlib 解压。分配前限制声明未压缩大小(≤ 512MB)——恶意声明不得触发 OOM;空/非法输入返回空数组。
zlib decompression. Caps the declared uncompressed size (≤ 512MB) before allocating — hostile declarations must not OOM; empty/invalid input returns an empty array.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
CompressedData | const TArray<uint8>& | 压缩字节。Compressed bytes. |
UncompressedSize | int32 | 声明解压大小(≤0 或超限返回空)。Declared uncompressed size (≤0 or over the cap → empty). |
Output
返回解压字节;失败返回空数组。
Returns decompressed bytes; empty on failure.
经 FMemoryWriter + SerializeItem 把 payload 序列化为字节(反射驱动)。
Serializes the payload to bytes via FMemoryWriter + SerializeItem (reflection-driven).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Payload | const FEntitySnapshotPayload& | 待序列化 payload。Payload to serialize. |
Output
返回序列化字节。
Returns the serialized bytes.
经 FMemoryReader + SerializeItem 反序列化;空输入或读取错误返回 false。
Deserializes via FMemoryReader + SerializeItem; empty input or read errors return false.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Bytes | const TArray<uint8>& | 序列化字节。Serialized bytes. |
OutPayload | FEntitySnapshotPayload& | 输出 payload。Output payload. |
Output
返回是否成功。副作用:成功时覆写 OutPayload。
Returns whether it succeeded. Side effect: overwrites OutPayload on success.
状态访问器:IsServerActive = 监听 socket 存在;IsClientConnected 仅当 socket 报告 SCS_Connected(UE 的 GetConnectionState 把连接中映射为 SCS_NotConnected、把对端优雅关闭映射为 SCS_Connected,无法检测远端关闭——由 Tick 的 recv 探测承担;为 false 时 RPC 处理器经 ConnectClient 重建,其会拆除残留死 socket);GetServerPort 返回实际端口;GetNextSnapshotId 递增分配传输 ID。
Status accessors: IsServerActive = listen socket exists; IsClientConnected only when the socket reports SCS_Connected (UE's GetConnectionState maps a pending connect to SCS_NotConnected and a gracefully-closed peer to SCS_Connected — it cannot detect remote closure; that duty belongs to Tick's recv probe; a false here makes RPC handlers re-establish via ConnectClient, which tears down stale sockets); GetServerPort returns the actual port; GetNextSnapshotId allocates transfer IDs incrementally.
Input
无参数。
No parameters.
Output
按访问器返回对应值;GetNextSnapshotId 副作用为递增内部计数器。
Returns the corresponding value per accessor; GetNextSnapshotId increments the internal counter as a side effect.
管线角色 Pipeline Role
创建与绑定:UMassBattleNetworkSubsystem::InitializeSnapshotTcpTransport 按网络模式创建(服务器:游戏端口 +100~+109 尝试 + Port=0 回退;客户端:惰性创建)并绑定三个委托——服务器侧 OnLockstepDataBlockUploaded 把上行块暂存并汇聚等待的引用命令(NetworkSubsystem.cpp:177-191);客户端侧 OnSnapshotBytesReceived 反序列化 + QueueSnapshotApply(:207-223)、OnLockstepDataBlockReceived 缓存下行块(:226-230)。
每帧驱动:UMassBattleNetworkSubsystem::TickSnapshotTcpTransport 调用 Transport->Tick()(NetworkSubsystem.cpp:265-272)并裁剪过期块。
快照下发:服务器 DispatchSnapshotViaTcp_Async(GameModeBase)后台线程序列化压缩 → 游戏线程 SendSnapshot → ClientBeginTcpSnapshotResync 控制平面带端口/令牌 → 客户端 ConnectClient → 认证 → FlushPendingSnapshotForPlayer 补发 → OnSnapshotBytesReceived → 应用。
槽位生命周期:AMassBattleNetworkGameModeBase::AllocatePlayerSlot 调 ReusePlayerSlot(GameModeBase.cpp:631),FreePlayerSlot 调 ReleasePlayerSlot(:651)——防止死 socket 跨登出残留污染重连;PostLogin 的 TCP 预连接握手经 ExpectPlayerSlot 白名单槽位并立即让客户端 ClientConnectTcpTransport(:629 附近)。
lockstep 自动分流:服务器 SendLockstepCommandToClients(≥16KB 负载)BroadcastLockstepDataBlock + 引用命令;客户端 SendLockstepCommandToServer SendLockstepDataBlockToServer 上行,服务器 ConvergeUplinkBlock 汇聚后回广播。
网络模拟:UMassBattleFuncLib::SetNetworkEmulation / ClearNetworkEmulation / GetNetworkEmulation(BP)调用本类 SetEmulationSettings / GetEmulationSettings,并同时设置 UE 的 net.PktLag 系列,使 TCP 与 UDP RPC 两条传输一起受影响(FuncLib.cpp:980-1009)。
Creation and binding: UMassBattleNetworkSubsystem::InitializeSnapshotTcpTransport creates per net mode (server: game port +100~+109 attempts with a Port=0 fallback; client: lazy) and binds the three delegates — server-side OnLockstepDataBlockUploaded stages the uplink block and converges any waiting reference command (NetworkSubsystem.cpp:177-191); client-side OnSnapshotBytesReceived deserializes + QueueSnapshotApplys (:207-223), and OnLockstepDataBlockReceived caches downlink blocks (:226-230).
Per-frame driving: UMassBattleNetworkSubsystem::TickSnapshotTcpTransport calls Transport->Tick() (NetworkSubsystem.cpp:265-272) and prunes stale blocks.
Snapshot delivery: server DispatchSnapshotViaTcp_Async (GameModeBase) serializes/compresses on a background thread → game-thread SendSnapshot → the ClientBeginTcpSnapshotResync control plane carries port/token → client ConnectClient → auth → FlushPendingSnapshotForPlayer delivery → OnSnapshotBytesReceived → apply.
Slot lifecycle: AMassBattleNetworkGameModeBase::AllocatePlayerSlot calls ReusePlayerSlot (GameModeBase.cpp:631); FreePlayerSlot calls ReleasePlayerSlot (:651) — so a dead socket cannot survive a logout and poison the next reconnect; the PostLogin TCP pre-connect handshake whitelists the slot via ExpectPlayerSlot and immediately has the client ClientConnectTcpTransport (around :629).
Lockstep auto-flood: the server's SendLockstepCommandToClients (≥16KB payload) uses BroadcastLockstepDataBlock + a reference command; the client's SendLockstepCommandToServer uploads via SendLockstepDataBlockToServer, and the server's ConvergeUplinkBlock converges and rebroadcasts.
Network emulation: UMassBattleFuncLib::SetNetworkEmulation / ClearNetworkEmulation / GetNetworkEmulation (BP) call this class's SetEmulationSettings / GetEmulationSettings and also set UE's net.PktLag family, impairing the TCP and UDP RPC transports together (FuncLib.cpp:980-1009).