You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX与Spring WebSocket收发文本/二进制消息异常求助

Spring WebSocket服务端返回消息无法被JavaFX客户端接收的排查方案

问题背景

作为JavaFX新手,开发了Spring WebSocket服务端以实现文本、二进制(图片、PDF等)消息的双向收发。目前服务端可接收两类消息,但客户端无法收到返回消息,需排查解决。


排查与修复方案

一、服务端代码问题修正

核心问题点

  1. 重复消息处理:继承AbstractWebSocketHandler后同时重写handleMessage和handleBinaryMessage/handleTextMessage,导致消息被重复处理;且复用原BinaryMessage发送时,payload流读取完毕后无法再次发送。
  2. 无效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方法,使用父类默认逻辑避免重复处理
}

二、客户端代码问题修正

核心问题点

  1. 二进制消息注解缺失:@OnMessage未明确指定接收二进制消息,可能无法匹配服务端发送的BinaryMessage。
  2. UI线程操作违规:在消息回调中直接修改JavaFX UI组件,违反JavaFX线程规则。
  3. 发送流未刷新:发送文件后未调用flush(),导致消息未完整发送到服务端。
  4. 重连逻辑不合理:连接关闭后立即重连,未添加延迟易导致频繁连接失败。

修改后的客户端代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 15:46:00