Every player connection is represented by aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/kenzz55/ue5-iocp-mmo-server/llms.txt
Use this file to discover all available pages before exploring further.
Session object that travels through a strictly ordered set of states. Session objects are never constructed or destroyed during normal operation—they live in a fixed-size SessionPool and are recycled through PrepareForReuse() when a connection closes. This design avoids dynamic allocation on the hot path and gives each session a stable pool slot index (used by the AoI visibility bitset).
Session States
Connecting
After
AcceptEx completes, HandleAcceptCompletion calls Session::Reset(), which sets the state to Connecting, assigns the new socket, and registers the session in the active sessions map. PostRecv is posted immediately so incoming data can arrive.Connected
Once the session is registered and the receive context is active, the state advances to
Connected. All normal packet traffic—login, game entry, chat, combat, inventory—occurs in this state. Rate limiting, heartbeat tracking, and job queuing are all live.Disconnecting
BeginDisconnect() atomically transitions the state to Disconnecting and posts DisconnectEx with TF_REUSE_SOCKET so the underlying socket can be recycled without a full OS-level close. Any sends already in flight are allowed to complete.Session Pool
SessionPool pre-allocates all sessions at startup, eliminating heap allocation during connection handling.
| Config constant | Value | Effect |
|---|---|---|
InitialSessionPoolSize | 1,024 | Sessions pre-allocated in Preallocate() at boot |
Acquire() pops a Session* from the internal free list and calls Reset() with the accepted socket and a fresh session ID. Release() calls PrepareForReuse() (which resets all state fields, clears queues, and resets atomic flags) and pushes the pointer back onto the free list.
Authentication Fields
Once a session isConnected, identity is tracked with three plain fields on the session object:
authenticated early in their handler and return S_ErrorRes with ERROR_CODE_AUTH_REQUIRED if the flag is not set.
Heartbeat Mechanism
The server sends periodic ping packets and disconnects sessions that stop responding.| Config constant | Value | Behaviour |
|---|---|---|
HeartbeatIntervalSeconds | 5 | Frequency at which the server sends PacketIdPing |
HeartbeatTimeoutSeconds | 15 | Age after which a session is considered dead and disconnected |
StartHeartbeatTimer() in IOCPServer uses TimerManager to schedule DriveHeartbeat() every HeartbeatIntervalSeconds seconds. DriveHeartbeat iterates all authenticated sessions, sends a Ping, and calls BeginDisconnect on any session whose heartbeat age exceeds HeartbeatTimeoutSeconds * 1000 ms.
Send Queue
Sends are always asynchronous. When a packet is enqueued while a send is already in flight, it sits in a mutex-protecteddeque until TryPostNextSend() is called from the IoType::Send completion:
TryPostNextSend() pops the next entry from sendQueue_ and posts a WSASend. If the queue is empty, sendPending_ is set to false so the next PostSend will start a new chain.
Rate Limiting
Incoming packet rate is enforced per session with a sliding window:| Config constant | Value | Behaviour |
|---|---|---|
MaxPacketsPerSecond | 100 | Maximum incoming packets per one-second window |
RecordIncomingPacket() returns false, the server sends S_ErrorRes with ERROR_CODE_RATE_LIMITED and discards the packet.
Job Queue
Work can be serialised onto a session’s logical thread to avoid locks:| Config constant | Value | Behaviour |
|---|---|---|
MaxSessionJobQueueSize | 1,024 | Jobs dropped (with metric increment) beyond this limit |
SessionJobWorkerCount | 4 | Worker threads in SessionJobScheduler |
PostJob pushes a PendingJob onto jobQueue_ and, if no job is currently running, enqueues the session into SessionJobScheduler. The scheduler calls DrainJobQueue, which pops and executes jobs in FIFO order while holding a lightweight in-progress flag.
Session Teardown
Normal teardown sequence:PrepareForReuse() clears authenticated, zeroes accountId/characterId, resets atomic flags, empties sendQueue_ and jobQueue_, and resets recvBuffer_. The socket handle itself is retained and reused by the next AcceptEx call.