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

> Discover how DD Poker's client-server architecture powers its online multiplayer chat system. Learn about message routing, UI decoupling, and all messaging types.

- Repository: [Doug Donohoe/ddpoker](https://github.com/dougdonohoe/ddpoker)
- Tags: internals
- Published: 2026-02-28

---

**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`](https://github.com/dougdonohoe/ddpoker/blob/main/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`](https://github.com/dougdonohoe/ddpoker/blob/main/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:

```java
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`](https://github.com/dougdonohoe/ddpoker/blob/main/code/poker/src/main/java/com/donohoedigital/games/poker/online/ChatHandler.java) (lines 47‑50):

```java
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:

```java
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`](https://github.com/dougdonohoe/ddpoker/blob/main/code/poker/src/main/java/com/donohoedigital/games/poker/online/ChatPanel.java)) registers itself in its constructor:

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

```

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

```java
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:

```java
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`](https://github.com/dougdonohoe/ddpoker/blob/main/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.