From 22990fa35e3b8c37e77e005b9de8e8f6811a3682 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 11 Mar 2026 11:37:04 +0100 Subject: [PATCH 1/4] Add: Document outlining the core components and roles in the server-side networking --- .../docs/networking/server-architecture.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 documents/docs/networking/server-architecture.md diff --git a/documents/docs/networking/server-architecture.md b/documents/docs/networking/server-architecture.md new file mode 100644 index 0000000..93ad908 --- /dev/null +++ b/documents/docs/networking/server-architecture.md @@ -0,0 +1,81 @@ +# Server-Side Networking — How It All Fits Together + +This doc is meant to give a solid mixed-level understanding of how our server-side networking works. No deep dives — just a clear picture of what's happening under the hood when a client connects and sends a command. + +> **Note:** This is v1. Annotations and Reflection-based dispatching are on the roadmap but not covered here yet. + +> **Disclamer:** This document has been written by Claude. I modified certain parts and verified its contents for correctness. + +## Our Protocol at a Glance + +Our protocol is inspired by **POP3** - a classic, text-based protocol that communicates over a raw TCP connection. The idea is simple: the client sends a command as a plain-text string, and the server responds with either a success or an error. + +A typical exchange looks something like this: + +``` +Client → GET_DELTA SINCE=42 +Server → +OK + PLAYER_FOLDED playerId=3 + POT 240 + NEXT_TURN playerId=1 + . +``` + +Responses start with `+OK` on success or `-ERR` when something goes wrong. Commands are short, uppercase strings - sometimes followed by arguments. + +We don't use HTTP, there's no JSON body, no headers. Just a raw socket, a text stream, and a clearly defined set of commands. + +## Core Components & Their Roles + +Here's a quick rundown of the main building blocks: + +### `NetworkManager` +This is the entry point. It binds to a specific port and listens for incoming TCP connections. +Once a client connects, it creates a `Session` adds it to the `SessionManager` and goes back to waiting. Its not the responsibility of the `NetworkManager` to recieve and send data from and to each connected client. + +### `Session` +Every client that connects has its own `Session` instance running on its own thread. This component owns the lifecycle of that connection: it reads incoming data, passes it along for processing, and writes responses back. + +### `SessionManager` +The `SessionManager` stores all currently active sessions. It allows for the retrieval of a specific session by its id. + +### `ProtocolParser` +Raw text (encapsulated in a `RawRequest`) coming off the socket isn't immediately useful — the `ProtocolParser` turns it with the help of the `Tokenizer` into a `PrimitiveRequest`. + +### `RawRequest` +The `RawRequest` object is created after a message has been recieved. +It holds both a reference to the `Session` that recieved the message and the message itself. + +### `PrimitiveRequest` +The `PrimitiveRequest` object is created by the `ProtocolParser` after the content of the `RawRequest` has been tokenized by the `Tokenizer`. +The command is stored in its own attribute and the arguments are accessible as a dictionary. + +### `Tokenizer` +The job of the `tokenizer` is it, to take the raw string and turn it into tokens. +For example, the command `CHAT_LOBBY GAME=12 MESSAGE='All-in?'` will be decoded as `"CHAT_LOBBY", "GAME", "=", "12", "MESSAGE", "=", "All-in?"`. +It is unaware about the meaning. + +### `CommandParser` (per command) +Takes in the `PrimitiveRequest` and checks if all required fields are provided with the correct value. +Creates a command specific `Request` object containing command arguments in a structured manner. + +### `Request` (per command) +Has fields common along each implementation such as a reference to the `Session` that recieved the request. +Furthermore each implementation has fields unique to each command. For example, the `ChatLobbyRequest` has fields for the game and the message. + +### `CommandRouter` +By identifying the type of class of the `Request`, the `CommandRouter` routes the request to the matching `CommandHandler`. + +### `CommandHandler` (per command) +Each supported command has its own handler — a small, focused class that contains the logic for that specific command. They have access to the domain, containing inner parts of the game itself. + +### `Response` (per response) +The interface has two sub-interfaces for either failed `ErrorResponse` or successfull `SuccessResponse` execution of commands. + +Each step has exactly one responsibility. Increasing the ability to test and extend different components later on. + +## Concurrency - Handling Multiple Clients + +Every incoming connection spawns a new **Thread**. This means multiple clients can be served simultaneously without blocking each other. + +As each `Session` runs entirely on its own thread, there's no shared mutable state between sessions, with exception of the `SessionManager` and other key components explained later. -- 2.52.0 From 32e4b04c87a31b288de398422ee7bed6baa9b9f8 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 11 Mar 2026 11:43:27 +0100 Subject: [PATCH 2/4] Add: SessionId to identify each session --- documents/docs/networking/server-architecture.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documents/docs/networking/server-architecture.md b/documents/docs/networking/server-architecture.md index 93ad908..1be14eb 100644 --- a/documents/docs/networking/server-architecture.md +++ b/documents/docs/networking/server-architecture.md @@ -36,6 +36,9 @@ Once a client connects, it creates a `Session` adds it to the `SessionManager` a ### `Session` Every client that connects has its own `Session` instance running on its own thread. This component owns the lifecycle of that connection: it reads incoming data, passes it along for processing, and writes responses back. +### `SessionId` +The `SessionId` is used to uniquely identify each session. Essentially its a wrapper arround the UUID type. + ### `SessionManager` The `SessionManager` stores all currently active sessions. It allows for the retrieval of a specific session by its id. -- 2.52.0 From fb6f14371ff191031ad56f33ae6ded8ccbbd33b9 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 11 Mar 2026 11:49:46 +0100 Subject: [PATCH 3/4] Add: PlantUML diagramm outlining each component --- .../networking_components.puml | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 documents/images/docs/networking/server-architecure/networking_components.puml diff --git a/documents/images/docs/networking/server-architecure/networking_components.puml b/documents/images/docs/networking/server-architecure/networking_components.puml new file mode 100644 index 0000000..647654a --- /dev/null +++ b/documents/images/docs/networking/server-architecure/networking_components.puml @@ -0,0 +1,96 @@ +@startuml +skinparam classAttributeIconSize 0 +skinparam packageStyle rectangle +skinparam linetype ortho +skinparam backgroundColor transparent + +' Network Layer +class NetworkManager { + + start(port: int) +} + +class Session { + - id: SessionId + + Session(id: SessionId) + + getId(): SessionId + + run() + + send(response: String) +} + +class SessionId { + - value: UUID + + SessionId() + + SessionId(value: UUID) +} + +class SessionManager { + - sessions: Map + + add(session: Session) + + getById(id: UUID): Session + + remove(id: UUID) +} + +NetworkManager --> Session : creates +NetworkManager --> SessionManager : adds session to +Session --> SessionId : identified by +Session --> RawRequest : creates +Session ..> Response : sends +SessionManager o-- Session : manages + +' Protocol Layer +class RawRequest { + - session: Session + - rawMessage: String +} + +class Tokenizer { + + tokenize(input: String): List +} + +class ProtocolParser { + + parse(raw: RawRequest): PrimitiveRequest +} + +class PrimitiveRequest { + - command: String + - arguments: Map +} + +RawRequest --> ProtocolParser : passed to +ProtocolParser --> Tokenizer : uses +ProtocolParser --> PrimitiveRequest : produces + +' Command Layer +interface CommandParser <> { + + parse(primitive: PrimitiveRequest): Request +} + +interface Request <> { + - session: Session +} + +class CommandRouter { + + route(request: Request) +} + +interface CommandHandler <> { + + handle(request: Request) +} + +PrimitiveRequest --> CommandParser : passed to +CommandParser --> Request : produces +CommandRouter --> CommandHandler : dispatches to +CommandRouter ..> Request : receives + +' Response Layer +interface Response { + + encode(): String +} + +interface SuccessResponse + +interface ErrorResponse + +Response <|-- SuccessResponse +Response <|-- ErrorResponse +@enduml -- 2.52.0 From a5272b99ab44785de568ebf13f9376fc3f9a7c89 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Wed, 11 Mar 2026 11:50:59 +0100 Subject: [PATCH 4/4] Add: Include diagramm in document --- documents/docs/networking/server-architecture.md | 2 ++ .../networking/server-architecure/networking_components.svg | 1 + 2 files changed, 3 insertions(+) create mode 100644 documents/images/docs/networking/server-architecure/networking_components.svg diff --git a/documents/docs/networking/server-architecture.md b/documents/docs/networking/server-architecture.md index 1be14eb..8537382 100644 --- a/documents/docs/networking/server-architecture.md +++ b/documents/docs/networking/server-architecture.md @@ -27,6 +27,8 @@ We don't use HTTP, there's no JSON body, no headers. Just a raw socket, a text s ## Core Components & Their Roles +![PlantUML diagramm of all components outlined in this document](/documents/images/docs/networking/server-architecure/networking_components.svg) + Here's a quick rundown of the main building blocks: ### `NetworkManager` diff --git a/documents/images/docs/networking/server-architecure/networking_components.svg b/documents/images/docs/networking/server-architecure/networking_components.svg new file mode 100644 index 0000000..9db9f6d --- /dev/null +++ b/documents/images/docs/networking/server-architecure/networking_components.svg @@ -0,0 +1 @@ +NetworkManager+start(port: int)Session-id: SessionId+Session(id: SessionId)+getId(): SessionId+run()+send(response: String)SessionId-value: UUID+SessionId()+SessionId(value: UUID)SessionManager-sessions: Map<UUID, Session>+add(session: Session)+getById(id: UUID): Session+remove(id: UUID)RawRequest-session: Session-rawMessage: StringResponse+encode(): StringTokenizer+tokenize(input: String): List<String>ProtocolParser+parse(raw: RawRequest): PrimitiveRequestPrimitiveRequest-command: String-arguments: Map<String, String>«per command»CommandParser+parse(primitive: PrimitiveRequest): Request«per command»Request-session: SessionCommandRouter+route(request: Request)«per command»CommandHandler+handle(request: Request)SuccessResponseErrorResponsecreatesadds session toidentified bycreatessendsmanagespassed tousesproducespassed toproducesdispatches toreceives \ No newline at end of file -- 2.52.0