JavaFX与Spring WebSocket收发文本/二进制消息异常求助
Spring WebSocket服务端返回消息无法被JavaFX客户端接收的排查方案
问题背景
作为JavaFX新手,开发了Spring WebSocket服务端以实现文本、二进制(图片、PDF等)消息的双向收发。目前服务端可接收两类消息,但客户端无法收到返回消息,需排查解决。
排查与修复方案
一、服务端代码问题修正
核心问题点
- 重复消息处理:继承
AbstractWebSocketHandler后同时重写handleMessage和handleBinaryMessage/handleTextMessage,导致消息被重复处理;且复用原BinaryMessage发送时,payload流读取完毕后无法再次发送。 - 无效session未过滤:发送消息时未判断session是否处于打开状态,可能导致发送失败且无有效错误日志。
修改后的服务端代码
package com.simplews.handler; import java.io.IOException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.BinaryMessage; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.AbstractWebSocketHandler; public class CustomWebSocketHandler extends AbstractWebSocketHandler { private static final Logger LOGGER = LoggerFactory.getLogger(CustomWebSocketHandler.class); private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); LOGGER.info("新连接建立:{}", session.getId()); super.afterConnectionEstablished(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); LOGGER.info("连接关闭:{},状态:{}", session.getId(), status); super.afterConnectionClosed(session, status); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); LOGGER.info("收到文本消息:{},来自会话:{}", payload, session.getId()); for (WebSocketSession webSocketSession : sessions) { if (webSocketSession.isOpen()) { try { webSocketSession.sendMessage(new TextMessage(payload)); } catch (IOException e) { LOGGER.error("发送文本消息给会话{}失败", webSocketSession.getId(), e); } } else { sessions.remove(webSocketSession); } } } @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws IOException { LOGGER.info("收到二进制消息,长度:{},来自会话:{}", message.getPayloadLength(), session.getId()); // 复制payload流,避免原流读取完毕后无法发送 BinaryMessage newMessage = new BinaryMessage(message.getPayload().duplicate()); if (session.isOpen()) { try { session.sendMessage(newMessage); } catch (IOException e) { LOGGER.error("发送二进制消息给会话{}失败", session.getId(), e); } } } // 删除自定义handleMessage方法,使用父类默认逻辑避免重复处理 }
二、客户端代码问题修正
核心问题点
- 二进制消息注解缺失:
@OnMessage未明确指定接收二进制消息,可能无法匹配服务端发送的BinaryMessage。 - UI线程操作违规:在消息回调中直接修改JavaFX UI组件,违反JavaFX线程规则。
- 发送流未刷新:发送文件后未调用
flush(),导致消息未完整发送到服务端。 - 重连逻辑不合理:连接关闭后立即重连,未添加延迟易导致频繁连接失败。
修改后的客户端代码
package com.client; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Application; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.stage.FileChooser; import javafx.stage.Stage; import jakarta.websocket.ClientEndpoint; import jakarta.websocket.ContainerProvider; import jakarta.websocket.DeploymentException; import jakarta.websocket.OnClose; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; import jakarta.websocket.WebSocketContainer; @ClientEndpoint public class JavaFXBinaryWsClient extends Application { private static final Logger LOGGER = Logger.getLogger(JavaFXBinaryWsClient.class.getName()); private ImageView imageView; private Session session; @OnOpen public void onOpen(Session session) { this.session = session; LOGGER.info("客户端连接成功,会话ID:{}", session.getId()); } // 明确指定接收二进制消息,设置10MB消息上限 @OnMessage(maxMessageSize = 1024 * 1024 * 10) public void onBinaryMessage(InputStream input) { LOGGER.info("收到二进制消息"); // JavaFX UI操作必须在主线程执行 Platform.runLater(() -> { Image image = new Image(input); imageView.setImage(image); }); } // 可选:添加文本消息接收方法用于测试 @OnMessage public void onTextMessage(String message) { LOGGER.info("收到文本消息:{}", message); } @OnClose public void onClose() { LOGGER.info("连接关闭,3秒后尝试重连..."); // 延迟重连避免频繁连接 new Thread(() -> { try { Thread.sleep(3000); connectToWebSocket(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); } @Override public void start(final Stage primaryStage) { connectToWebSocket(); Button btn = new Button(); btn.setText("发送图片!"); btn.setPrefSize(400, 27); btn.setOnAction(event -> selectAndSendImage(primaryStage)); imageView = new ImageView(); imageView.setFitHeight(400); imageView.setFitWidth(400); imageView.setPreserveRatio(true); imageView.setSmooth(true); AnchorPane root = new AnchorPane(); AnchorPane.setTopAnchor(btn, 0.0); AnchorPane.setLeftAnchor(btn, 0.0); AnchorPane.setRightAnchor(btn, 0.0); AnchorPane.setTopAnchor(imageView, 27.0); AnchorPane.setBottomAnchor(imageView, 0.0); AnchorPane.setLeftAnchor(imageView, 0.0); AnchorPane.setRightAnchor(imageView, 0.0); root.getChildren().addAll(btn, imageView); Scene scene = new Scene(root, 400, 427); primaryStage.setTitle("图片传输"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } private void selectAndSendImage(Stage stage) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("选择要发送的图片"); File file = fileChooser.showOpenDialog(stage); if (file == null) return; try (InputStream input = new FileInputStream(file); OutputStream output = session.getBasicRemote().getSendStream()) { byte[] buffer = new byte[1024]; int read; while ((read = input.read(buffer)) > 0) { output.write(buffer, 0, read); } output.flush(); // 确保消息完整发送 LOGGER.info("图片发送完成:{}", file.getName()); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "发送图片失败", ex); } } private void connectToWebSocket() { WebSocketContainer container = ContainerProvider.getWebSocketContainer(); container.setDefaultMaxBinaryMessageBufferSize(1024 * 1024 * 10); container.setDefaultMaxTextMessageBufferSize(1024 * 1024); try { URI uri = URI.create("ws://localhost:8080/web-socket"); container.connectToServer(this, uri); } catch (DeploymentException | IOException ex) { LOGGER.log(Level.SEVERE, "连接WebSocket服务端失败", ex); } } }
三、额外排查步骤
- 验证端点配置:确认Spring Boot中通过
@EnableWebSocket和WebSocketConfigurer正确注册了CustomWebSocketHandler,端点映射路径与客户端一致。 - 查看服务端日志:启用DEBUG级别日志,检查是否有发送消息的异常信息。
- 第三方工具测试:使用Postman、wscat等工具连接服务端发送消息,确认服务端返回逻辑正常,排除客户端代码问题。
- 跨域检查:若客户端与服务端域名不同,确认服务端已配置跨域支持。
内容的提问来源于stack exchange,提问作者dinu0101
相关产品推荐
相关产品推荐

