跳转到内容

WebSocket 客户端

WebSocket 客户端用于建立双向实时通信连接,支持文本和二进制消息收发,以及完整的生命周期管理。

使用 Feat 工厂方法创建 WebSocket 连接:

import tech.smartboot.feat.Feat;
import tech.smartboot.feat.core.client.WebSocketListener;
Feat.websocket("ws://localhost:8080/chat", new WebSocketListener() {
@Override
public void onOpen(WebSocketClient client, WebSocketResponse response) {
System.out.println("连接已建立");
}
@Override
public void onMessage(WebSocketClient client, String message) {
System.out.println("收到消息: " + message);
}
});
WebSocketClient client = new WebSocketClient("ws://localhost:8080/chat");
client.options()
.debug(true)
.connectTimeout(5000)
.readBufferSize(8192);
client.connect(new WebSocketListener() {
@Override
public void onOpen(WebSocketClient client, WebSocketResponse response) {
System.out.println("连接成功!");
}
@Override
public void onMessage(WebSocketClient client, String message) {
System.out.println("收到: " + message);
}
@Override
public void onClose(WebSocketClient client, WebSocketResponse response, CloseReason reason) {
System.out.println("连接关闭: " + reason);
}
@Override
public void onError(WebSocketClient client, Throwable error) {
error.printStackTrace();
}
});
WebSocketClient client = new WebSocketClient("ws://localhost:8080");
client.options()
.debug(true) // 开启调试日志
.connectTimeout(5000) // 连接超时(毫秒)
.readTimeout(30000) // 读取超时(毫秒)
.readBufferSize(8192) // 读缓冲区大小
.writeBufferSize(8192) // 写缓冲区大小
.maxFrameSize(65536); // 最大帧大小
import tech.smartboot.feat.core.common.HeaderName;
WebSocketClient client = new WebSocketClient("ws://localhost:8080");
client.header(header -> header
.set(HeaderName.USER_AGENT, "Feat-WebSocket-Client")
.set(HeaderName.AUTHORIZATION, "Bearer token123"));
client.connect(listener);
@Override
public void onOpen(WebSocketClient client, WebSocketResponse response) {
try {
// 发送文本消息
client.sendMessage("Hello, WebSocket!");
client.sendMessage("{\"type\":\"chat\",\"content\":\"hello\"}");
// 发送二进制消息
byte[] data = "Binary data".getBytes(StandardCharsets.UTF_8);
client.sendBinary(data);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onMessage(WebSocketClient client, String message) {
// 处理文本消息
System.out.println("收到文本: " + message);
}
@Override
public void onMessage(WebSocketClient client, byte[] message) {
// 处理二进制消息
System.out.println("收到二进制: " + message.length + " bytes");
}

以下泳道图展示了 WebSocket 连接过程中各回调的触发时机:

sequenceDiagram
    autonumber
    participant App as 应用程序
    participant Client as WebSocket Client
    participant Server as WebSocket 服务器

    App->>Client: connect() 发起连接
    Client->>Server: WebSocket Handshake
    
    alt 连接成功
        Server-->>Client: Handshake Response (101)
        Client-->>App: onOpen()
        
        loop 双向通信
            App->>Client: sendMessage()
            Client->>Server: WebSocket Frame
            Server-->>Client: WebSocket Frame
            Client-->>App: onMessage()
        end
        
        Server-->>Client: Close Frame
        Client-->>App: onClose()
    else 连接失败
        Client-->>App: onError()
        Client-->>App: onClose()
    end

回调触发顺序说明:

  1. 发起连接 - 调用 connect() 后,客户端发送 WebSocket 握手请求
  2. 连接成功 - 握手成功(HTTP 101)时触发 onOpen(),此时可以开始发送消息
  3. 接收消息 - 收到服务端消息时触发 onMessage()
  4. 连接失败 - 握手失败或网络异常时触发 onError()
  5. 连接关闭 - 无论成功或失败,连接关闭时都会触发 onClose()
import tech.smartboot.feat.core.client.WebSocketClient;
import tech.smartboot.feat.core.client.WebSocketListener;
import tech.smartboot.feat.core.common.codec.websocket.CloseReason;
public class ChatClient {
private WebSocketClient client;
public void connect(String url) throws IOException {
client = new WebSocketClient(url);
client.options().debug(true).connectTimeout(5000);
client.connect(new WebSocketListener() {
@Override
public void onOpen(WebSocketClient client, WebSocketResponse response) {
System.out.println("[连接成功] " + response.getStatusCode());
try {
client.sendMessage("{\"type\":\"join\",\"room\":\"lobby\"}");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onMessage(WebSocketClient client, String message) {
System.out.println("[收到消息] " + message);
}
@Override
public void onClose(WebSocketClient client, WebSocketResponse response, CloseReason reason) {
System.out.println("[连接关闭] Code: " + reason.getCode());
}
@Override
public void onError(WebSocketClient client, Throwable error) {
System.err.println("[错误] " + error.getMessage());
}
});
}
public void send(String message) throws IOException {
if (client != null && client.isConnected()) {
client.sendMessage(message);
}
}
public void close() throws IOException {
if (client != null) {
client.close(1000, "Normal closure");
}
}
}

WebSocket 是长连接,程序不会自动退出,需要显式关闭。

// 正常关闭
client.close(1000, "Normal closure");
// 强制关闭
client.close();
public void onClose(WebSocketClient client, WebSocketResponse response, CloseReason reason) {
if (reconnectAttempts < MAX_RECONNECT) {
reconnectAttempts++;
System.out.println("连接断开,尝试重连 (" + reconnectAttempts + "/" + MAX_RECONNECT + ")");
try {
Thread.sleep(3000);
doConnect(); // 重新建立连接
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码名称说明
1000Normal Closure正常关闭
1001Going Away服务端或客户端离开
1002Protocol Error协议错误
1006Abnormal Closure异常关闭(连接意外断开)
1009Message Too Big消息太大
1011Internal Error服务端内部错误
EchoWebSocketServer.java
import tech.smartboot.feat.Feat;
import tech.smartboot.feat.core.server.WebSocketRequest;
import tech.smartboot.feat.core.server.WebSocketResponse;
import tech.smartboot.feat.core.server.upgrade.websocket.WebSocketUpgrade;
public class EchoWebSocketServer {
public static void main(String[] args) {
Feat.httpServer().httpHandler(req -> {
req.upgrade(new WebSocketUpgrade(65536) {
@Override
public void handleTextMessage(WebSocketRequest request, WebSocketResponse response, String data) {
System.out.println("收到: " + data);
response.sendTextMessage("Echo: " + data);
}
});
}).listen(8080);
}
}
InteractiveWebSocketClient.java
import tech.smartboot.feat.core.client.WebSocketClient;
import tech.smartboot.feat.core.client.WebSocketListener;
import tech.smartboot.feat.core.common.codec.websocket.CloseReason;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class InteractiveWebSocketClient {
private static WebSocketClient client;
public static void main(String[] args) throws Exception {
client = new WebSocketClient("ws://localhost:8080");
client.options().debug(true);
client.connect(new WebSocketListener() {
@Override
public void onOpen(WebSocketClient client, WebSocketResponse response) {
System.out.println("已连接到服务器,输入消息(输入 'exit' 退出):");
}
@Override
public void onMessage(WebSocketClient client, String message) {
System.out.println("服务器: " + message);
}
@Override
public void onClose(WebSocketClient client, WebSocketResponse response, CloseReason reason) {
System.out.println("连接已关闭: " + reason);
}
});
// 读取用户输入
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = reader.readLine()) != null) {
if ("exit".equalsIgnoreCase(line)) {
client.close(1000, "User requested");
break;
}
client.sendMessage(line);
}
}
}

连接失败

检查清单:

  1. URL 是否为 ws://wss://
  2. 服务端是否支持 WebSocket 升级
  3. 端口和路径是否正确

sendMessage 抛出 IOException

连接未就绪或已断开。只在 onOpen 回调后发送消息:

if (client.isConnected()) {
client.sendMessage("data");
}

程序不退出

WebSocket 是长连接,需要手动关闭:

// 正常关闭
client.close(1000, "Normal closure");
// 强制关闭
client.close();