How DD Poker's Server-Client Communication Architecture Works: A Deep Dive into the Network Layer

DD Poker implements a hybrid network stack that combines TCP/HTTP servlets for administrative traffic, a custom reliability layer over UDP for real-time game actions, and optional P2P sockets for direct peer connections.

The open-source DD Poker codebase (dougdonohoe/ddpoker) demonstrates a sophisticated, multi-protocol approach to online gaming networking. This article examines the actual Java source to explain how the server-client communication architecture balances low-latency gameplay with robust administrative functionality.

The Three-Layer Hybrid Architecture

DD Poker's network stack is divided into three distinct layers optimized for specific traffic types:

  • TCP/HTTP Servlet Layer: Handles web UI, account management, and static resources through standard Java servlets.
  • UDP Game Transport: A custom, connection-oriented UDP protocol for game actions, chat, and test connections.
  • P2P Overlay: Optional direct client-to-client sockets for NAT traversal and private tables.

TCP/HTTP Administrative Services

The server extends GameServer (located in code/server/src/main/java/com/donohoedigital/server/GameServer.java), an abstract TCP server that creates ServerSocketChannel instances and routes HTTP requests to a BaseServlet. This layer manages the lobby interface, user accounts, and static resource delivery, leveraging standard servlet container robustness for non-time-critical operations.

Custom UDP Transport Layer

For latency-critical gameplay, DD Poker implements a custom UDP stack centered around UDPServer (code/udp/src/main/java/com/donohoedigital/udp/UDPServer.java). This class runs a selector on one or more DatagramChannel instances and manages UDPLink objects representing individual client connections.

The PokerServer class (code/pokerserver/src/main/java/com/donohoedigital/games/poker/server/PokerServer.java) implements both UDPLinkHandler and UDPManagerMonitor to receive game data and forward it to the engine. Similarly, ChatServer (code/pokerserver/src/main/java/com/donohoedigital/games/poker/server/ChatServer.java) utilizes this same UDP infrastructure for chat traffic.

Reliability Over UDP

Because UDP lacks built-in reliability, the architecture implements UDPLink and UDPManager (see UDPLink.java and UDPManager.java) to handle acknowledgments, retransmission logic, and timeout detection. This creates a connection-oriented abstraction over the connectionless UDP protocol.

Optional P2P Sockets

For scenarios requiring direct client-to-client communication—such as private tables or NAT traversal—the codebase provides Peer2PeerClient (code/server/src/main/java/com/donohoedigital/p2p/Peer2PeerClient.java). This non-blocking SocketChannel wrapper manages DNS resolution and connection timeouts, while Peer2PeerMessenger handles status encoding and decoding.

Client-Side Connection Initialization

The desktop client entry point, PokerMain (code/poker/src/main/java/com/donohoedigital/games/poker/PokerMain.java), initializes the network stack by creating a UDPServer in client mode, registering itself as a UDPLinkHandler, and launching the Swing UI.

When connecting to a server, the PokerConnect class (code/pokernetwork/src/main/java/com/donohoedigital/games/poker/network/PokerConnect.java) orchestrates the handshake. It creates a UDPLink, packages an initial OnlineMessage containing license and version data into a PokerUDPTransporter, and blocks on a WaitBoolean until the server replies or times out.

// Create the UDP manager (usually obtained from a UDPServer instance)
UDPServer udp = new UDPServer(myHandler, false);
udp.init();               // bind sockets, start selector thread
udp.start();

// Build the connect URL (host:port) and the initial OnlineMessage
PokerURL url = new PokerURL("poker.example.com", 7777);
OnlineMessage connectMsg = new OnlineMessage();
connectMsg.setWanAuth(authData);   // license / version info

// Perform the handshake
PokerConnect connector = new PokerConnect(udp, url, null /*listener*/);
boolean ok = connector.connect(connectMsg);
if (ok) {
    OnlineMessage reply = connector.getReply();
    // proceed – reply contains server‑assigned session info
}

Server-Side Message Handling

The server processes incoming UDP packets through the monitorEvent method in PokerServer. When a packet arrives, the server validates the license key and version information, then constructs an OnlineMessage reply referencing the original message ID via setInReplyTo.

public void monitorEvent(UDPLinkEvent event) {
    if (event.getType() == UDPLinkEvent.Type.RECEIVED) {
        UDPData data = event.getData();
        if (data.getType() == UDPData.Type.MESSAGE) {
            PokerUDPTransporter pkt = new PokerUDPTransporter(data);
            OnlineMessage msg = new OnlineMessage(pkt.getMessage());

            // Validate license, version, etc.
            EngineMessage validate = PokerServlet.validateKeyAndVersion(...);
            if (validate != null) {
                sendError(event.getLink(), validate.getApplicationErrorMessage());
                return;
            }

            // Build positive reply and queue it back
            OnlineMessage reply = new OnlineMessage();
            reply.setInReplyTo(msg.getMessageID());
            reply.setData(new DDMessage(...));
            event.getLink().queue(new PokerUDPTransporter(reply.getData()).getData());
            event.getLink().send();
        }
    }
}

Peer-to-Peer Connection Setup

For direct peer connections, clients instantiate Peer2PeerClient using a P2PURL. The connection is non-blocking and includes timeout handling through a dedicated selector thread.

P2PURL p2pUrl = new P2PURL("peer.example.com", 8888);
Peer2PeerClient p2p = new Peer2PeerClient(p2pUrl, msgListener, connListener);
p2p.connect();                     // non‑blocking connect with timeout handling
// After connection:
msgListener.updateStep(DDMessageListener.STEP_CONNECTED);

Complete Communication Flow

The lifecycle of a DD Poker network session follows these sequential steps:

  1. Client Startup: PokerMain initializes a client-mode UDPServer and registers as a UDPLinkHandler and UDPManagerMonitor.
  2. Handshake Initiation: PokerConnect.connect(OnlineMessage) obtains a UDPLink from the local UDPManager, attaches a monitor, and transmits the initial authentication message.
  3. Server Validation: PokerServer receives the packet via monitorEvent, validates credentials through PokerServlet.validateKeyAndVersion(), and queues a reply to the originating UDPLink.
  4. Client Acknowledgment: PokerConnect receives the reply, verifies the inReplyTo field matches the original request, and unblocks the waiting thread. If the status is OK, the client enters the lobby.
  5. Game Messaging: Post-handshake, both entities exchange game-specific messages (bets, folds, chat) over the established UDPLink. The server forwards these to the EngineServer, while the client routes them to the UI through p2pMessageReceived.
  6. Direct Peer Exchange: For private tables, clients activate Peer2PeerClient to establish direct sockets, bypassing the central server for specific data streams.

Summary

  • DD Poker employs a hybrid architecture combining HTTP servlets, custom UDP transports, and optional P2P sockets.
  • TCP/HTTP layers in GameServer handle administrative traffic, while UDP layers in UDPServer manage real-time gameplay.
  • The UDP reliability layer (UDPLink, UDPManager) implements acknowledgments and retransmissions over connectionless UDP.
  • Client connections are initiated via PokerConnect, which blocks until the server replies or times out.
  • Server processing occurs in PokerServer, which implements UDPLinkHandler to validate clients and route game data.
  • Optional P2P functionality via Peer2PeerClient enables direct client-to-client communication for NAT traversal scenarios.

Frequently Asked Questions

Why does DD Poker use UDP instead of TCP for game traffic?

UDP provides lower latency for real-time poker actions, avoiding TCP's head-of-line blocking and congestion control delays. According to the source code in UDPLink.java and UDPManager.java, the implementation handles packet loss through custom acknowledgment and retransmission logic while maintaining UDP's speed advantages.

How does the server validate incoming client connections?

The server validates connections in the PokerServer.monitorEvent() method by inspecting the OnlineMessage payload. It calls PokerServlet.validateKeyAndVersion() to verify license keys and client versions before sending an acknowledgment reply via the UDPLink.queue() and send() methods.

What is the purpose of the Peer2PeerClient in the architecture?

Peer2PeerClient enables direct socket connections between clients, bypassing the central server for certain features like private tables or NAT traversal scenarios. It wraps non-blocking SocketChannel objects and handles DNS resolution with timeout awareness, as implemented in Peer2PeerClient.java.

How does the client handle connection timeouts during the initial handshake?

The PokerConnect class implements a blocking handshake mechanism using a WaitBoolean. When calling connect(OnlineMessage), the client queues the transport packet and blocks until the server's reply triggers monitorEvent, which sets the boolean and unblocks the waiting thread. If no reply arrives within the configured timeout, the connection attempt fails gracefully.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →