How DD Poker Handles Network Communication: UDP vs TCP Protocols

DD Poker maintains two independent networking stacks—TCP for reliable, connection-oriented services like lobby management and file transfers, and UDP with a custom reliability layer for low-latency game traffic and real-time chat.

The open-source DD Poker application (dougdonohoe/ddpoker) implements a dual-protocol architecture to optimize for different network requirements. By handling UDP versus TCP protocols through completely separate code paths, the application minimizes latency for real-time gameplay while ensuring data integrity for administrative functions. Both stacks share common configuration patterns and logging infrastructure but implement distinct life-cycle management, threading models, and reliability mechanisms.

TCP Stack Architecture

The TCP implementation provides guaranteed, ordered delivery for operations requiring data integrity over speed.

Server Initialization and Binding

The GameServer class in code/server/src/main/java/com/donohoedigital/server/GameServer.java initializes the TCP listener. The _init() method opens one or more ServerSocketChannels, binds them to ports defined in settings.tcp.* entries (typically from code/pokerserver/src/main/resources/config/poker/server.properties), and registers them with a Selector for non-blocking I/O operations.

A single selector thread runs the accept loop, while a ThreadPool handles the actual game logic for each connection. The server uses Utils.getLocalAddressPort(ServerSocketChannel) from code/common/src/main/java/com/donohoedigital/base/Utils.java for diagnostic logging of bound addresses.

Connection Handling

Per-connection management occurs in PokerTCPServer (extending Peer2PeerServer). When a client connects, the server accepts the SocketChannel, creates a PokerConnection, and passes Peer2PeerMessage objects to the game engine. Sending data requires no additional framing logic—messages write directly to the channel via PokerTCPServer.write().

// In PokerMain – force TCP usage for lobby or file transfer
PokerConnectionServer tcpSrv = getPokerConnectionServer(false);
// tcpSrv is an instance of PokerTCPServer (extends Peer2PeerServer)

Built-in Reliability

TCP guarantees in-order delivery, retransmission, and flow control at the transport layer. DD Poker leverages these native features without implementing additional acknowledgment logic. The Peer2PeerMessage.read() and Peer2PeerMessage.write() methods interact directly with the SocketChannel stream.

UDP Stack and Custom Reliability

The UDP implementation prioritizes speed for latency-sensitive operations, implementing its own reliability mechanisms on top of the connectionless protocol.

Datagram Channel Management

The UDPServer class in code/udp/src/main/java/com/donohoedigital/udp/UDPServer.java creates DatagramChannels bound to ports specified by settings.udp.port and settings.udp.chat.port in code/poker/src/main/resources/config/poker/client.properties. The _init() method configures non-blocking channels registered with a Selector, reading raw datagrams into reusable ByteBuffer instances.

Diagnostic helpers in Utils.java provide getLocalAddressPort(DatagramChannel) for logging UDP endpoint information, mirroring the TCP utility functions.

Application-Level Reliability

Since UDP provides no delivery guarantees, UDPManager in code/udp/src/main/java/com/donohoedigital/udp/UDPManager.java implements a custom reliability layer:

  • Acknowledgment Tracking: Every data packet pairs with PING_ACK or MTU_ACK responses. The manager periodically executes processSendAcksPing() to transmit ACK bursts and processSendAll() to retransmit unacknowledged packets.
  • Link State Management: UDPLink maintains lists of unacked UDPData chunks with exponential back-off timing. The requiresExistingLink() method validates session continuity via sessionID_ fields in UDPMessage headers.
  • Health Monitoring: Stale peers trigger timeouts, and UDPLinkHandler callbacks notify the application of connection state changes.
// Inside UDPManager.run() – reliability loop
if (msg == SENDACK) processSendAcksPing();   // periodic ACK burst
else if (msg == SENDALL) processSendAll();   // retransmit pending packets

Threading and Dispatch

The UDP architecture uses a three-thread model:

  1. Server Thread: UDPServer reads datagrams from the kernel and queues UDPMessage objects.
  2. Manager Thread: UDPManager processes the LinkedBlockingQueue, handles retransmission timers, and invokes UDPLinkHandler callbacks.
  3. Timer Tasks: Periodic tasks drive the ACK and retransmission cycles.

Protocol Selection Logic

The PokerMain class in code/poker/src/main/java/com/donohoedigital/games/poker/PokerMain.java serves as the decision point for network communication protocol selection. The getPokerConnectionServer(boolean bUDP) method returns a UDP server when the flag is true (default for game traffic) and a TCP server otherwise.

// Snippet from PokerMain – selecting server type
if (bUDP) {
    p2p_ = getCreateUDPServer();   // UDP path for game traffic
} else {
    p2p_ = getTCPServer();         // TCP path for lobby/file transfer
}

Chat Services: Always use UDP via getChatServer(), which returns a PokerUDPServer, prioritizing low latency over guaranteed delivery for text messages.

LAN Discovery: Uses a separate multicast UDP listener (LanManager) that operates independently of both primary stacks.

Message Transmission Patterns

Sending over TCP

TCP message transmission operates through direct channel writes:

Peer2PeerMessage p2p = new Peer2PeerMessage(Peer2PeerMessage.P2P_MSG, myDDMessage);
int bytesSent = tcpSrv.send(p2pConnection, p2p);   // PokerTCPServer.write()

The PokerTCPServer.send() method writes directly to the SocketChannel without additional buffering or framing beyond the Peer2PeerMessage serialization.

Sending over UDP

UDP transmission delegates to the manager's work queue:

// Build a UDPMessage and hand it to the manager
udpSrv.manager().addMessage(myUDPMessage);

UDPManager.addMessage() places the message on its internal queue. The manager thread later serializes the UDPMessage (encoding headers and UDPData chunks) and transmits via DatagramChannel, handling retransmission automatically if acknowledgments fail to arrive within the back-off window.

Configuration and Port Management

Both stacks utilize property-driven configuration but use distinct key patterns:

  • TCP Ports: Defined by generic settings.tcp.* entries in code/pokerserver/src/main/resources/config/poker/server.properties
  • UDP Ports: Explicitly defined by settings.udp.port and settings.udp.chat.port in code/poker/src/main/resources/config/poker/client.properties

The Utils class provides socket diagnostic helpers for both protocols, enabling consistent logging of local addresses and ports regardless of transport type.

Summary

  • DD Poker implements separate TCP and UDP stacks to optimize for reliability versus latency requirements.
  • TCP handles lobby services and file transfers using standard ServerSocketChannel and SocketChannel APIs with built-in transport reliability.
  • UDP manages real-time game traffic and chat through DatagramChannel with a custom acknowledgment and retransmission layer in UDPManager.
  • Protocol selection occurs in PokerMain.getPokerConnectionServer(), which instantiates PokerTCPServer or PokerUDPServer based on the bUDP parameter.
  • Configuration uses distinct property keys (settings.tcp.* versus settings.udp.port) across server and client property files.

Frequently Asked Questions

Why does DD Poker use both UDP and TCP instead of just one protocol?

DD Poker uses TCP for operations requiring guaranteed delivery like file transfers and lobby management, where latency matters less than data integrity. For real-time game traffic and chat, UDP minimizes latency and jitter, while the custom UDPManager reliability layer handles packet loss through application-level acknowledgments rather than TCP's head-of-line blocking.

How does DD Poker ensure reliable delivery over UDP?

The UDPManager class implements exponential back-off retransmission, pairing every data packet with PING_ACK or MTU_ACK responses via processSendAcksPing() and processSendAll() methods. UDPLink tracks unacknowledged UDPData chunks and session IDs, removing stale peers when timeouts exceed configured thresholds.

What determines whether a connection uses UDP or TCP in DD Poker?

The PokerMain.getPokerConnectionServer(boolean bUDP) method acts as the selection logic. When bUDP is true (the default for active gameplay), the system calls getCreateUDPServer() to instantiate PokerUDPServer; when false, it calls getTCPServer() for PokerTCPServer. Chat services explicitly use UDP through getChatServer(), while LAN discovery uses a separate multicast UDP listener.

How are ports configured for each protocol in DD Poker?

TCP ports are read from generic settings.tcp.* entries in code/pokerserver/src/main/resources/config/poker/server.properties, while UDP ports use explicit keys settings.udp.port and settings.udp.chat.port defined in code/poker/src/main/resources/config/poker/client.properties. Both protocols use Utils.getLocalAddressPort() for diagnostic logging of bound channels.

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 →