Backend Development 11 min read

Detailed Explanation of Java WebSocket Client Development

This article provides a comprehensive walkthrough of building a Java WebSocket client, covering client creation, connection establishment, message sending, graceful closure, header handling, and code encapsulation, with complete example code and best practices for performance testing and robust error handling.

FunTester
FunTester
FunTester
Detailed Explanation of Java WebSocket Client Development

5.2 Java-WebSocket Detailed Explanation

This chapter focuses on the WebSocket workflow, guiding readers from theoretical concepts to practical Java code. It covers client creation, connection establishment, message transmission, connection termination, and header processing, offering reusable examples for performance‑testing scenarios.

5.2.1 Creating a WebSocket Client

To build a WebSocket client, use the org.java_websocket.client.WebSocketClient class, which provides a simple and flexible constructor. The default Draft_6455 protocol implementation ensures compatibility with modern WebSocket standards.

public WebSocketClient(URI serverUri) {
    this(serverUri, (Draft)(new Draft_6455()));
}

Below is a sample client implementation that demonstrates lifecycle management:

package org.funtester.performance.books.chapter05.section2;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * FunTester WebSocket client example
 */
public class WebSocketClientDemo {
    public static void main(String[] args) throws URISyntaxException {
        String uri = "ws://localhost:12345/websocket/FunTester"; // server address
        URI serverUri = new URI(uri);
        WebSocketClient webSocketClient = new WebSocketClient(serverUri) {
            /**
             * Triggered when the connection is opened; prints handshake info
             */
            @Override
            public void onOpen(ServerHandshake serverHandshake) {
                System.out.println("FunTester: 连接已打开");
                serverHandshake.iterateHttpFields().forEachRemaining(x ->
                    System.out.println("握手信息 key: " + x + " value: " + serverHandshake.getFieldValue(x)));
            }

            /**
             * Triggered when a message is received from the server
             */
            @Override
            public void onMessage(String s) {
                System.out.println("FunTester: 收到消息: " + s);
            }

            /**
             * Triggered when the connection is closed; logs the reason
             */
            @Override
            public void onClose(int i, String s, boolean b) {
                System.out.println("FunTester: 连接已关闭,状态码: " + i + ", 原因: " + s + ", 是否由服务端关闭: " + b);
                close(); // actively close client connection
            }

            /**
             * Triggered on error; prints stack trace and closes the connection
             */
            @Override
            public void onError(Exception e) {
                System.out.println("FunTester: 连接异常");
                e.printStackTrace();
                close(1, "FunTester客户端异常关闭");
            }

            /**
             * Custom close logic with additional logging
             */
            @Override
            public void close() {
                super.close();
                System.out.println("FunTester: 连接已关闭");
            }
        };
    }
}

The code logs key events (open, message, close, error) to aid debugging and monitoring. Overriding close() adds extra log output, which is valuable in performance tests to trace client behavior.

5.2.2 Establishing the WebSocket Connection

After creating the client, invoke org.java_websocket.client.WebSocketClient#connect to start the connection. The method spawns a dedicated thread and does not return a success flag, so the client state must be checked separately.

public void connect() {
    if (connectReadThread != null)
        throw new IllegalStateException("WebSocketClient objects are not reusable");
    connectReadThread = new Thread(this);
    connectReadThread.setName("WebSocketConnectReadThread-" + connectReadThread.getId());
    connectReadThread.start();
}

A retry loop can improve robustness under network fluctuations:

System.out.println("FunTester: 正在连接...");
webSocketClient.connect();
int retryCount = 0;
while (true) {
    if (retryCount++ > 3) {
        System.out.println("FunTester: 连接失败,超出最大重试次数");
        break;
    }
    ThreadTool.sleep(1000); // wait 1s
    if (webSocketClient.isOpen()) {
        System.out.println("FunTester: WebSocket客户端状态: " + webSocketClient.getReadyState().name());
        break;
    }
}
System.out.println("FunTester: 连接成功");

Typical console output demonstrates the handshake details and successful connection. After a successful connection the Java process remains alive because the read thread continues running; testers must close the client explicitly.

The org.java_websocket.client.WebSocketClient#reconnect method can be used for automatic reconnection after a failure:

public void reconnect() {
    reset();
    connect();
}

In practice, set reconnection intervals and limits to avoid endless retries, especially in chaos‑engineering tests.

5.2.3 Sending Messages

Message transmission uses org.java_websocket.client.WebSocketClient#send(String) . Example:

webSocketClient.send("Hello FunTester WebSocket!"); // send message

The server echoes the message, triggering onMessage to print the response. Heartbeat (ping) messages are also essential to keep the connection alive:

webSocketClient.sendPing(); // send Ping to maintain activity

In performance testing, periodic pings prevent server‑side timeouts and help evaluate the server's capacity under sustained load.

5.2.4 Closing the Connection

Closing is performed via org.java_websocket.client.WebSocketClient#close . Common overloads include close() , close(int code) , and close(int code, String message) . In performance tests the parameterless close() is usually sufficient.

Calling close() after the onClose and onError logic ensures resources are released. When testing high‑concurrency disconnections, repeatedly invoking close() can simulate many clients dropping simultaneously.

5.2.5 Header Information Handling

WebSocket connections can include custom HTTP headers. Two approaches are supported:

Set headers via the constructor: public WebSocketClient(URI serverUri, Map httpHeaders) { this(serverUri, new Draft_6455(), httpHeaders); }

Dynamic addition using addHeader : webSocketClient.addHeader("name", "FunTester"); webSocketClient.addHeader("password", "123456");

Headers can be removed with removeHeader . Because headers are stored in a Map<String, String> , duplicate keys are not allowed, which differs from the raw HTTP header array. In performance tests, custom headers are useful for authentication or passing metadata without incurring extra traffic after the handshake.

5.2.6 Client Encapsulation

Directly using WebSocketClient works but leads to repetitive code in large test suites. Encapsulating the client into a reusable class improves maintainability and reduces boilerplate, especially when many performance‑testing scenarios require similar setup and teardown logic.

JavaWebSocketNetworkingClientPerformanceTesting
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.