JavaFX客户端无法正确连接Spring Boot STOMP WebSocket主题排查
问题排查与解决:Spring Boot STOMP WebSocket + JavaFX Tyrus客户端异常
问题现象
- JavaFX客户端通过Tyrus连接Spring Boot STOMP WebSocket服务,连接成功但仅收到零散字符(如'o'、'h')
- 手动发送的订阅消息无效,无法获取
/events主题的正常消息 - 连接外部测试WebSocket(
wss://ws.blockchain.info/inv)时出现相同异常 - 前端Angular使用现成库可正常连接服务
服务器端配置(Spring Boot)
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer { private MessageBrokerRegistry registry; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/socket") .setAllowedOrigins("*") .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.setApplicationDestinationPrefixes("/news") .enableSimpleBroker("/events") ; } public MessageBrokerRegistry getRegistry() { return this.registry; } }
客户端代码(JavaFX + Tyrus)
BasicClient核心代码
@ClientEndpoint(configurator = CustomClientEndpointConfigurator.class, decoders = MessageDecoder.class, encoders = MessageEncoder.class ) public class BasicClient implements MessageHandler { // 省略部分代码 @SuppressWarnings("unused") @OnOpen public void onOpen(Session session) throws IOException { this.session = session; String[] strings = new String[1]; strings[0] = "\"SUBSCRIBE id:sub-34 destination:/events\""; session.getAsyncRemote().sendText(Arrays.toString(strings)); // 省略部分代码 } // 省略部分代码 }
GeneralController调用代码
public class GeneralController { private BasicClient basicClient; @FXML void initialize() throws IOException { try { basicClient = BasicClient.getInstance(); basicClient.connect(); // 省略监听逻辑 } catch (Exception e) { indicatorConnect.setStyle("-fx-background-color: red;"); e.printStackTrace(); } } }
手动构造的Socket路径
public static final String LOCALHOST_WEBSOCKET_CONNECT = "/socket/%s/%s/websocket"; public static String createURISocketEDMS() throws URISyntaxException { // 省略路径构造逻辑 return String.format("ws://localhost:8080"+LOCALHOST_WEBSOCKET_CONNECT,username,valueStr); }
问题原因分析
- SockJS协议不兼容:服务器端开启了SockJS,但客户端用原生WebSocket直接连接,SockJS返回的框架字符(如'o'代表心跳、'h'代表握手响应)被当作普通消息接收,导致看到零散字符。
- STOMP消息格式错误:手动构造的订阅消息不符合STOMP规范,STOMP要求消息格式为
COMMAND\nheader1:value1\nheader2:value2\n\nbody^@,而非简单的字符串拼接。 - Tyrus功能局限:Tyrus是原生WebSocket客户端,无法自动处理SockJS的封装逻辑,而Angular使用的STOMP库会自动完成SockJS握手和消息格式转换。
解决方法
方案一:服务器端关闭SockJS(纯STOWS模式)
修改WebSocketConfiguration的registerStompEndpoints方法,移除withSockJS():
@Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/socket") .setAllowedOrigins("*"); }
客户端连接路径改为ws://localhost:8080/socket,无需构造复杂路径。
方案二:客户端替换为支持SockJS+STOMP的库
放弃Tyrus,使用专门的STOMP客户端库,比如Spring官方的STOMP Client:
StompClient stompClient = StompClient.create("ws://localhost:8080/socket"); stompClient.connectHeaders(new HttpHeaders()) .subscribe(session -> { session.subscribe("/events", message -> { // 处理收到的消息 System.out.println("Received: " + message.getPayloadAsString()); }); });
方案三:修复Tyrus客户端的消息格式与SockJS处理(不推荐)
如果坚持使用Tyrus:
- 修正STOMP订阅消息格式:
String stompSubscribe = "SUBSCRIBE\nid:sub-34\ndestination:/events\n\n\0"; session.getAsyncRemote().sendText(stompSubscribe);
注意消息结尾必须加\n\n\0(空行+NULL终止符)。
2. 解析SockJS框架消息:收到的消息会被SockJS包裹在数组中(如["STOMP_MESSAGE_CONTENT"]),需要先剥离外层数组格式,再解析内部的STOMP消息。
额外验证提示
连接wss://ws.blockchain.info/inv时,该服务是纯WebSocket协议而非STOMP,需按照其要求发送JSON格式的订阅命令(如{"op":"unconfirmed_sub"}),而非STOMP的SUBSCRIBE消息。
内容的提问来源于stack exchange,提问作者Daniel Vai
相关产品推荐
相关产品推荐

