How the DD Poker Online Multiplayer System Establishes and Manages Connections
DD Poker uses Java NIO non-blocking sockets and a lightweight peer-to-peer messaging protocol, coordinated through GameServer and OnlineManager to handle TCP connections, validate join requests, and manage player reconnects.
The online multiplayer layer in the open-source DD Poker project (dougdonohoe/ddpoker) implements a high-performance networking stack built on standard Java NIO components. The architecture separates transport concerns from game logic, using abstract interfaces to support both TCP and optional UDP transports while maintaining a single-threaded selector loop for accepting connections and a thread pool for message processing.
Server-Side Connection Architecture
TCP Server Implementation (GameServer)
The core TCP listener is implemented in code/server/src/main/java/com/donohoedigital/server/GameServer.java. This class binds listening sockets, accepts incoming channels, and manages the NIO event loop.
Key operations include:
ServerSocketChannel.open()andbind()(lines 345‑353) – Binds oneServerSocketChannelper available IP address using the configured port (default5000).- Selector registration (lines 73‑74) – Registers each channel for
OP_ACCEPToperations. processSelection()(lines 36‑44) – Handleskey.isAcceptable()events by callingserver.accept()to create newSocketChannelinstances.registerChannel(channel, SelectionKey.OP_READ)(line 54) – Hands accepted sockets to the worker pool for read operations.
The server applies TCP optimizations immediately upon acceptance, including TCP_NODELAY, SO_LINGER, and custom buffer sizes to minimize latency for real-time poker actions.
Concrete Implementations (PokerTCPServer and PokerUDPServer)
The game-specific logic resides in PokerTCPServer, an inner class of PokerMain (lines 720‑740 in code/poker/src/main/java/com/donohoedigital/games/poker/PokerMain.java). This concrete implementation of the Peer2PeerServer interface is instantiated when a user clicks "Host Game" in the UI.
For experimental UDP support, PokerUDPServer (in code/poker/src/main/java/com/donohoedigital/games/poker/PokerUDPServer.java) implements the same PokerConnectionServer interface. It listens on a UDP port, creates Link objects for remote endpoints, and feeds datagrams into the same OnlineManager pipeline used by TCP connections.
Client-Side Connection Bootstrap
Peer2PeerClient Implementation
The client-side connection logic lives in code/p2p/src/main/java/com/donohoedigital/p2p/Peer2PeerClient.java. This class handles DNS timeouts, non-blocking connect operations, and socket configuration.
The connection sequence follows these steps:
SocketChannel.open()(line 42) – Creates a new non-blocking channel.- Socket configuration (lines 44‑51) – Applies
TCP_NODELAY,SO_LINGER, and buffer sizes matching the server configuration. connect(addr_)(lines 63‑99) – Initiates a non-blocking connection to the host address, looping onfinishConnect()with a configurable timeout to handle slow networks gracefully.
Once connected, the client exchanges Peer2PeerMessage objects with the server, wrapping game-specific actions in a standardized transport format.
Message Routing and Processing
SocketThread Worker Pool
Accepted channels are not processed on the selector thread. Instead, GameServer.processChannel() assigns each readable channel to a SocketThread from the worker pool (implemented in code/server/src/main/java/com/donohoedigital/server/SocketThread.java).
The worker executes processChannel(channel), which:
- Reads raw bytes from the
SocketChannel. - Deserializes the stream into
Peer2PeerMessageobjects. - Wraps messages in
DDMessageTransporterinstances. - Forwards them to
OnlineManager.handleMessage()for game-level processing.
This design decouples I/O selection from message parsing, preventing slow clients from blocking new connection acceptance.
Session Management and Validation
OnlineManager Responsibilities
The OnlineManager class (code/poker/src/main/java/com/donohoedigital/games/poker/online/OnlineManager.java) serves as the high-level multiplayer coordinator. It validates join requests, tracks active player sockets via PokerConnection wrappers, and handles mid-game reconnects.
Critical methods include:
joinGame()(lines 96‑107) – Builds aJOINmessage and transmits it via the P2P messenger to request entry into a hosted game.handleMessage()– Receives inbound messages, validates them throughvalidate(), and dispatches to specific handlers likeprocessJoin()orprocessQuit().processJoin()(lines 70‑90) – Detects duplicate connections from reconnecting players, closes stale sockets, and re-binds the new channel to the existingPokerPlayerobject without disrupting game state.connectionClosing()– Invoked when a socket closes unexpectedly, triggering cleanup of player state and notifying remaining participants.
Connection Lifecycle Walkthrough
The DD Poker online multiplayer system follows a strict lifecycle from server startup to graceful shutdown:
- Server initialization –
GameServer.init()readssettings.server.port(default5000) and bindsServerSocketChannelinstances to all available local IP addresses. - Connection acceptance – When the selector reports
OP_ACCEPT,GameServercreates a newSocketChannel, applies TCP options, and registers it forOP_READ. - Worker assignment –
GameServer.processChannel()obtains aSocketThreadfrom the pool and passes the channel for message reading. - Client connection – The UI instantiates
Peer2PeerClient(orPokerP2PHeadlessfor automated clients), invokesconnect(), and transmits aJOINmessage viaOnlineManager.joinGame(). - Validation –
OnlineManager.validate()verifies game ID, password, and version compatibility before assigning aPokerPlayerto the socket. - Reconnection handling – If a player reconnects using the same identity during an active game,
processJoin()closes the oldSocketChanneland associates the new one with the existing player session. - Graceful shutdown –
GameServer.shutdown()wakes the selector, closes all listening channels, and invokes servletdestroy()methods, whileOnlineManager.connectionClosing()handles individual player disconnections.
Implementation Examples
Starting a Host Server
// In PokerMain.startServer()
GameServer server = new GameServer() {
@Override
protected SocketThread newSocketThread() {
return new PokerSocketThread(this);
}
};
server.setAppName("DDPoker");
server.setServlet(new PokerServlet()); // Forwards to OnlineManager
server.init(); // Binds ports, creates selector
server.start(); // Runs selector loop in dedicated thread
Source: code/poker/src/main/java/com/donohoedigital/games/poker/PokerMain.java (lines 720‑740)
Connecting as a Client
Peer2PeerClient client = new Peer2PeerClient(
hostIp,
hostPort,
new ClientMessageListener(), // Handles server messages
new UIMessageListener() // Updates UI thread
);
client.connect(); // Non-blocking connect with timeout handling
OnlineManager manager = new OnlineManager(game);
Object result = manager.joinGame(observe, reconnect, false);
if (result instanceof DDMessage) {
// Handle join error (wrong password, game full, etc.)
}
Source: code/p2p/src/main/java/com/donohoedigital/p2p/Peer2PeerClient.java (lines 63‑99)
Processing Server-Side Messages
@Override
protected void processChannel(SocketChannel channel) throws IOException {
Peer2PeerMessage msg = new Peer2PeerMessage();
msg.read(channel); // Deserializes from channel
DDMessageTransporter reply = onlineManager.handleMessage(msg, channel);
if (reply != null) {
reply.write(channel); // Sends response back to client
}
}
Source: code/server/src/main/java/com/donohoedigital/server/SocketThread.java
Graceful Server Shutdown
server.shutdown(); // Wakes selector, closes ServerSocketChannels,
// terminates worker threads, and notifies servlets
Source: code/server/src/main/java/com/donohoedigital/server/GameServer.java (lines 74‑88)
Key Source Files
| File Path | Description |
|---|---|
code/server/src/main/java/com/donohoedigital/server/GameServer.java |
Core TCP server implementing the NIO selector loop, socket binding, and connection acceptance. |
code/p2p/src/main/java/com/donohoedigital/p2p/Peer2PeerClient.java |
Client bootstrap handling non-blocking connect, DNS timeouts, and socket configuration. |
code/poker/src/main/java/com/donohoedigital/games/poker/online/OnlineManager.java |
High-level multiplayer coordinator managing join validation, reconnects, and player state. |
code/poker/src/main/java/com/donohoedigital/games/poker/PokerMain.java |
UI entry point hosting PokerTCPServer inner class and launching the online manager. |
code/poker/src/main/java/com/donohoedigital/games/poker/PokerUDPServer.java |
Optional UDP transport sharing the PokerConnectionServer interface contract. |
code/server/src/main/java/com/donohoedigital/server/SocketThread.java |
Worker thread pool implementation for reading and deserializing socket data. |
code/poker/src/main/java/com/donohoedigital/games/poker/network/PokerConnection.java |
Wrapper around SocketChannel used by OnlineManager to track player identities. |
Summary
- DD Poker uses Java NIO non-blocking sockets with a dedicated selector thread for accepting connections and a worker pool for message processing.
GameServerhandles low-level TCP binding and channel registration, whileOnlineManagerimplements game-specific session logic and validation.Peer2PeerClientmanages client-side connection establishment with configurable timeouts and TCP optimizations likeTCP_NODELAY.- The system supports transparent reconnection by detecting duplicate player keys in
processJoin()and swapping socket references without game interruption. - Both TCP and UDP transports implement the
PokerConnectionServerinterface, allowingOnlineManagerto operate transport-agnostically.
Frequently Asked Questions
How does DD Poker handle player reconnections without restarting the game?
When a player reconnects, OnlineManager.processJoin() detects the duplicate player key and invokes connectionClosing() on the stale socket. It then re-associates the new SocketChannel with the existing PokerPlayer object (lines 70‑90 in OnlineManager.java), preserving game state and hand history while seamlessly transitioning the player back into the active session.
What port does DD Poker use for multiplayer connections?
By default, the server binds to port 5000 as defined in settings.server.port within GameServer.init(). The system attempts to bind this port on all available local IP addresses, and can be configured to use alternative ports through the application settings before starting the host server.
Why does DD Poker use non-blocking NIO instead of traditional blocking sockets?
The architecture uses Java NIO non-blocking channels to prevent slow or malicious clients from blocking the main acceptance thread. The single selector thread handles OP_ACCEPT events rapidly, handing off actual message reading to the SocketThread worker pool. This design supports hundreds of concurrent connections without thread-per-client overhead, critical for peer-to-peer poker hosting on consumer hardware.
How are messages serialized between client and server?
Messages are serialized using Peer2PeerMessage objects that implement custom read() and write() methods on SocketChannel instances. The SocketThread deserializes raw bytes into these message objects, wraps them in DDMessageTransporter containers, and forwards them to OnlineManager.handleMessage() for game-logic processing, ensuring type-safe communication between peers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →