How the Chat System Works in Online Multiplayer Games: Inside DD Poker's Client-Server Architecture

DD Poker implements a three-layer client-server architecture where OnlineManager processes OnlineMessage objects to route chat through TCP or UDP networks, delivering them to decoupled UI components via the ChatHandler interface to support global, table-scoped, and private messaging.

The chat system in online multiplayer games requires careful separation between network transport and UI rendering to maintain real-time performance across varying connection qualities. In the dougdonohoe/ddpoker repository, this challenge is solved through a publish/subscribe model that processes chat messages through distinct network, message-processing, and UI layers while supporting both TCP production traffic and UDP testing environments.

Three-Layer Architecture Overview

DD Poker's chat implementation follows a strict decoupling principle across three distinct layers:

  1. Network Layer – Handles raw packet ingress through ChatServer (UDP testing) or the standard P2P messenger (TCP production), forwarding data to the processing layer without touching UI code.
  2. Message-Processing Layer – The OnlineManager class validates incoming OnlineMessage objects of category CAT_CHAT, determines broadcast scope, and queues messages for the UI.
  3. UI Layer – Components implementing the ChatHandler interface (such as ChatPanel and ChatLobbyPanel) receive processed messages via chatReceived() and render them using Swing.

This architecture ensures that networking code never directly references Swing classes, allowing the system to queue messages when UI components are not yet initialized.

Network Ingress and Transport Handling

Raw chat messages enter the system through two primary pathways depending on the deployment context.

UDP Testing Entry Point

In code/pokerserver/src/main/java/com/donohoedigital/games/poker/server/ChatServer.java (lines 31‑44), the ChatServer class implements UDPLinkHandler and UDPManagerMonitor to receive low-level UDP packets. When a packet arrives with user type PokerConstants.USERTYPE_CHAT, the server forwards the raw data into the host's OnlineManager through the standard P2P messaging pipeline.

Production TCP Transport

For standard online play, chat travels over the same TCP-based peer-to-peer messenger that carries all other game state. The PokerUDPServer class provides additional UDP handling for beta features, but production chat relies on the reliable OnlineMessage transport already established for game synchronization.

Message Routing and Processing in OnlineManager

The OnlineManager class in code/poker/src/main/java/com/donohoedigital/games/poker/online/OnlineManager.java serves as the central hub for all chat logic, implementing the ChatManager interface to coordinate message flow.

Message Reception and Validation

When an OnlineMessage with category CAT_CHAT arrives, the processChat method (lines 671‑682) validates the sender and determines routing strategy:

private void processChat(OnlineMessage omsg) {
    boolean bDisplayLocally = true;
    if (isHost()) {
        // Forward to all participants unless it is a private message.
        if (omsg.getChatType() != PokerConstants.CHAT_PRIVATE) {
            PokerPlayer sender = getPlayer(omsg, true);
            bDisplayLocally = sendMessageChat(omsg, sender);
        }
    } else {
        // Client‑side: handle possible clock‑pause command.
        Boolean bPauseClock = omsg.isClockPaused();
        if (bPauseClock != null) {
            if (bPauseClock) game_.getGameClock().pause();
            else           game_.getGameClock().unpause();
        }
    }
    if (bDisplayLocally) deliverChat(omsg);
}

Broadcast and Table Scoping

The sendMessageChat method (lines 712‑731) implements the routing logic that distinguishes between global lobby chat and table-specific conversations. If chat.getTableNumber() equals OnlineMessage.NO_TABLE, the method calls sendMessageAllExcept to broadcast to all connected clients; otherwise, it invokes sendMessageTable to target only players seated at the specified table.

Delivery to UI Handlers

The deliverChat method (lines 737‑766) manages the final handoff to the presentation layer. If a ChatHandler is currently registered via setChatHandler, the message passes immediately to chatReceived(). Otherwise, the system stores the message in chatQueue_ until a UI component registers itself.

UI Decoupling via ChatHandler

The chat system achieves true decoupling through the ChatHandler interface defined in code/poker/src/main/java/com/donohoedigital/games/poker/online/ChatHandler.java (lines 47‑50):

public interface ChatHandler {
    void chatReceived(OnlineMessage omsg);
}

Handler Registration and Message Flushing

When UI components such as ChatPanel or ChatLobbyPanel initialize, they register themselves with OnlineManager.setChatHandler (lines 771‑788). This method synchronizes on the pending queue and immediately flushes any messages that arrived before the UI was ready:

public void setChatHandler(ChatHandler chat) {
    synchronized (chatQueue_) {
        chat_ = chat;
        for (OnlineMessage pending : chatQueue_) {
            chat_.chatReceived(pending);
        }
        chatQueue_.clear();
    }
}

Concrete UI Implementation

The ChatPanel class (line 161 in code/poker/src/main/java/com/donohoedigital/games/poker/online/ChatPanel.java) registers itself in its constructor:

if (mgr_ != null) mgr_.setChatHandler(this);

When users press Enter, the panel captures input and forwards it to the manager:

protected void sendChat() {
    String sMsg = chatInputField.getText().trim();
    if (!sMsg.isEmpty() && mgr_ != null) {
        // Send to the host; the current table scope is used.
        mgr_.sendChat(sMsg, game_.getCurrentTable(), null);
        chatInputField.setText("");
    }
}

Client-to-Server Message Flow

The complete transmission path from user input to remote clients follows this sequence:

  1. User Input: ChatPanel.sendChat() (line 504) constructs the message string.
  2. Manager Transmission: OnlineManager.sendChat(String sMessage, PokerTable table, String sTestData) (lines 2041‑2050) creates an OnlineMessage, sets the table number (or NO_TABLE for global chat), and transmits it over the network.
  3. Host Processing: The server-side OnlineManager receives the OnlineMessage, executes processChat(), and calls sendMessageChat() to determine routing.
  4. Client Delivery: Remote clients receive the broadcast message, their local OnlineManager processes it, and the registered ChatHandler updates the Swing text area.

Special Message Types and Host Privileges

The system distinguishes between player-generated chat and administrative system messages through type constants defined in PokerConstants (e.g., CHAT_PRIVATE, CHAT_ADMIN_JOIN, CHAT_ADMIN_ERROR).

Dealer and Administrative Chat

Only the host may broadcast dealer chat messages using sendDealerChat (lines 521‑558), which enforces this privilege strictly:

public void sendDealerChat(int nType, String sMessage, PokerTable table) {
    if (!local.isHost())
        throw new ApplicationError(ErrorCodes.ERROR_UNSUPPORTED,
                                   "sendDealerChat only allowed from host", sMessage);
    OnlineMessage chat = new OnlineMessage(OnlineMessage.CAT_CHAT_ADMIN);
    chat.setChat(sMessage);
    chat.setChatType(nType);
    chat.setPlayerInfo(local.getPlayerInfo());   // host info
    // Target a specific table or all
    chat.setTableNumber(table != null ? table.getNumber() : OnlineMessage.NO_TABLE);
    sendMessageChat(chat, null);
    deliverChat(chat);   // also show locally
}

Clock Control Integration

Chat messages can carry game control commands. In processChat (lines 683‑702), clients check omsg.isClockPaused() to pause or unpause the game clock, enabling hosts to manage game flow through chat commands.

Summary

  • Decoupled Architecture: The system strictly separates network transport (ChatServer), message processing (OnlineManager), and UI rendering (ChatPanel) to prevent threading issues and support message queuing.
  • Flexible Routing: sendMessageChat distinguishes between global broadcast (NO_TABLE) and table-scoped delivery using the table number field in OnlineMessage.
  • Registration Pattern: UI components implement ChatHandler and register via setChatHandler, which atomically flushes the chatQueue_ of any pending messages.
  • Privileged Operations: Host-only methods like sendDealerChat enforce authority through explicit checks, while special chat types in PokerConstants enable private and administrative messaging.

Frequently Asked Questions

How does DD Poker route chat messages to specific tables versus all players?

The sendMessageChat method in OnlineManager checks chat.getTableNumber() to determine scope. If the value is OnlineMessage.NO_TABLE, the system calls sendMessageAllExcept to broadcast globally; otherwise, it invokes sendMessageTable to deliver only to clients associated with that specific table number.

What prevents non-host players from sending dealer chat messages?

The sendDealerChat method (lines 521‑558) contains an explicit guard clause that verifies local.isHost(). If a non-host client attempts to invoke this method, the system throws an ApplicationError with code ERROR_UNSUPPORTED, ensuring only the host can broadcast administrative messages.

How does the chat system handle UI components that aren't ready to receive messages?

OnlineManager maintains a synchronized chatQueue_ list. When deliverChat is called but no handler is registered (via setChatHandler), the message stores in the queue. Once a UI component registers itself, setChatHandler atomically flushes all queued messages to the new handler before clearing the queue.

Does DD Poker use TCP or UDP for production chat traffic?

While the codebase includes a ChatServer that handles UDP packets for testing purposes (lines 31‑44 in ChatServer.java), production online multiplayer games use the standard TCP-based P2P messenger that carries all OnlineMessage objects, ensuring reliable delivery of chat alongside game state synchronization.

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 →