跳转到内容

连接第一个 WebSocket 服务

如果说 HttpClient 适合请求-响应式通信,SSE 适合服务端单向推送,那么 WebSocket 适合真正的长连接实时交互。
这篇文档不打算做完整协议百科,而是带你把第一条连接建起来。

先判断一下你的场景是不是它:

  • 客户端和服务端需要双向实时通信
  • 消息会持续来回交互,而不是一次请求一次响应
  • 你要做聊天室、协作编辑、游戏、实时控制台这类长连接场景

如果只是服务端持续推送,而客户端不需要主动发消息,先看 SSE 客户端教程

如果你手头还没有 WebSocket 服务,可以先用这个最小回声服务器本地自测:

SimpleWebSocketServer.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 SimpleWebSocketServer {
public static void main(String[] args) {
Feat.httpServer().httpHandler(req -> req.upgrade(new WebSocketUpgrade(2000) {
@Override
public void handleTextMessage(WebSocketRequest request, WebSocketResponse response, String data) {
response.sendTextMessage(data);
}
})).listen(8080);
}
}

它的行为很简单:你发什么,它回什么。

客户端最短写法如下:

WebSocketDemo.java
import tech.smartboot.feat.core.client.WebSocketClient;
import tech.smartboot.feat.core.client.WebSocketListener;
import tech.smartboot.feat.core.client.WebSocketResponse;
import tech.smartboot.feat.core.common.codec.websocket.CloseReason;
import java.io.IOException;
public class WebSocketDemo {
public static void main(String[] args) throws IOException {
WebSocketClient client = new WebSocketClient("ws://localhost:8080");
client.connect(new WebSocketListener() {
@Override
public void onOpen(WebSocketClient client, WebSocketResponse response) {
try {
client.sendMessage("hello world");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void onMessage(WebSocketClient client, String message) {
System.out.println("received: " + message);
}
@Override
public void onClose(WebSocketClient client, WebSocketResponse response, CloseReason reason) {
System.out.println("closed: " + reason);
}
});
}
}

这个例子其实已经覆盖了 WebSocket 客户端的完整最小生命周期:

  1. 创建客户端
  2. 建立连接
  3. onOpen(...) 后发送消息
  4. onMessage(...) 里接收服务端消息
  5. 最后由连接关闭或手动关闭收尾

为什么消息要放在 onOpen(...) 之后再发

Section titled “为什么消息要放在 onOpen(...) 之后再发”

因为只有握手成功以后,连接才真的可用。
在 Feat 的客户端模型里,onOpen(...) 就是你可以安全开始发第一条消息的时机。

如果服务端是上面的回声服务器,那么客户端会打印:

received: hello world

只要看到这一行,就说明下面几件事已经全部成立:

  • WebSocket 握手成功
  • 客户端成功发出了文本消息
  • 服务端成功处理并返回
  • 客户端监听器成功收到了回包

文本消息最常见:

client.sendMessage("Hello, Feat!");
client.sendMessage("{\"type\":\"chat\",\"content\":\"hello\"}");

如果你要发二进制数据,可以用:

byte[] binaryData = "Binary Data".getBytes();
client.sendBinary(binaryData);

这类用法更适合自定义协议、音视频片段或压缩后的消息体。

如果你想看连接过程,或者需要改一些连接参数:

WebSocketClient client = new WebSocketClient("ws://localhost:8080");
client.options()
.debug(true)
.connectTimeout(5000)
.readBufferSize(8192)
.writeBufferSize(8192);

这些配置里,最常用的通常是:

  • debug(true):本地排查连接和收发过程
  • connectTimeout(...):防止长时间握手卡住

如果你不想分两步写 new WebSocketClient(...)connect(...),Feat 还提供了便捷入口:

import tech.smartboot.feat.Feat;
Feat.websocket("ws://localhost:8080", new WebSocketListener() {
@Override
public void onOpen(WebSocketClient client, WebSocketResponse response) {
try {
client.sendMessage("hello");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});

它更适合写非常短的 demo。
如果你还要进一步调 options(),那还是显式创建 WebSocketClient 更清楚。

如果你的客户端是长期运行的程序,记得在合适的时候手动关闭连接:

client.close();

如果需要带关闭码和原因:

client.close(1000, "normal shutdown");

优先检查:

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

通常意味着连接还没准备好,或者已经断开。
最稳妥的做法是:只在 onOpen(...) 之后发送第一条消息。

我明明收到了消息,但程序不退出

Section titled “我明明收到了消息,但程序不退出”

这是正常的。
WebSocket 是长连接,不会像普通 HTTP 请求那样自动结束。你要自己决定何时关闭。

那通常不是 WebSocket 的最佳入口,先看 SSE 客户端教程