Qt客户端无法接收Spring Boot STOMP订阅消息排查求助
问题:Spring Boot STOMP服务器与Qt客户端通信,客户端收不到广播消息
服务器端实现
@Controller class GreetingController { @MessageMapping("/hello") @SendTo("/topic/greetings") @Throws(Exception::class) fun greeting(@RequestBody message : HelloMessage) : Greeting { Thread.sleep(1000); // simulated delay return Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!") } }
Qt客户端关键代码
StompClient发送帧方法
bool StompClient::sendFrame(std::shared_ptr<StompFrame> stompFrame) { if (!stompFrame->isBinaryBody()) { QString str = stompFrame->convertToQString(); m_Socket.sendTextMessage(str); qDebug() << "Send stomp frame " << str; return true; } QByteArray ba = stompFrame->convertToByteArray(); m_Socket.sendBinaryMessage(ba); return true; }
StompFrameCreator创建订阅/发送帧方法
std::shared_ptr<StompFrame> StompFrameCreator::createSubscribeFrame(const QString& destination) { auto stompFrame = std::make_shared<StompFrame>(); stompFrame->setCommand(StompFrame::HeaderTypes::SUBSCRIBE); stompFrame->addHeader("destination", destination); return stompFrame; } std::shared_ptr<StompFrame> StompFrameCreator::createSendTextFrame(const QString& destination, const QString& message) { auto stompFrame = std::make_shared<StompFrame>(); stompFrame->setCommand(StompFrame::HeaderTypes::SEND); stompFrame->addHeader("destination", destination); stompFrame->addHeader("content-type", "application/json"); stompFrame->addTextBody(message); return stompFrame; }
StompDemoView业务逻辑方法
void StompDemoView::sendGreeting() { qDebug() << "Send greeting"; StompFrameCreator stompFrameCreator; QJsonObject jsonObject; jsonObject.insert("name", QJsonValue::fromVariant(m_NameLineEdit->text())); QJsonDocument doc(jsonObject); auto publishFrame = stompFrameCreator.createSendTextFrame("/app/hello", doc.toJson()); qDebug() << "Sending " << doc.toJson(); m_StompClient.sendFrame(publishFrame); } void StompDemoView::clientConnected() { StompFrameCreator stompFrameCreator; qDebug() << "Subscribe to topic"; auto subscribeFrame = stompFrameCreator.createSubscribeFrame(0, "/topic/greetings", "auto"); m_StompClient.sendFrame(subscribeFrame); }
已确认的服务器日志流程
订阅请求接收日志
Decoded SUBSCRIBE {id=[0], destination=[/topic/greetings], ack=[auto]} session=null From client: SUBSCRIBE /topic/greetings id=0 session=6cc3a2b6-48a5-1c1c-2366-d746224d60e8 Processing SUBSCRIBE /topic/greetings id=0 session=6cc3a2b6-48a5-1c1c-2366-d746224d60e8
消息处理与广播日志
Decoded SEND {destination=[/app/hello], content-type=[application/json]} session=null application/json payload={ "name": "Cris" } From client: SEND /app/hello session=6cc3a2b6-48a5-1c1c-2366-d746224d60e8 application/json payload={ "name": "Cris" } Searching methods to handle SEND /app/hello session=6cc3a2b6-48a5-1c1c-2366-d746224d60e8 application/json payload={ "name": "Cris" } , lookupDestination='/hello' Found 1 handler methods: [{[MESSAGE],[/hello]}] Invoking GreetingController#greeting[1 args] Arguments: [HelloMessage@3903b632] Processing return value with SendToMethodReturnValueHandler [annotationRequired=true] Processing MESSAGE destination=/topic/greetings session=6cc3a2b6-48a5-1c1c-2366-d746224d60e8 payload={"content":"Hello, Cris!"} Broadcasting to 1 sessions.
排查方向
验证STOMP帧格式合规性
- 检查客户端发送的订阅帧是否严格遵循STOMP规范:帧结尾必须是
\n\n\0(换行+空行+空字节),确保convertToQString/convertToByteArray方法正确生成该结束符 - 通过抓包工具查看WebSocket传输的原始数据,确认服务器广播的MESSAGE帧格式是否正确
- 检查客户端发送的订阅帧是否严格遵循STOMP规范:帧结尾必须是
检查WebSocket连接与信号槽
- 在
errorOccurred槽函数中打印具体错误信息,排除连接异常 - 确认信号槽连接成功:打印
connect调用的返回值,或在textMessageReceived/binaryMessageReceived槽中添加日志,验证是否有数据触发信号 - 持续检查QWebSocket的状态是否为
QAbstractSocket::ConnectedState,避免连接意外断开
- 在
核对STOMP会话与订阅信息
- 确认服务器广播时的session ID与客户端当前会话ID一致,避免会话不匹配导致消息无法推送
- 检查订阅的
destination是否完全匹配:/topic/greetings的拼写、大小写、路径层级必须和服务器广播目标一致
调试客户端STOMP解析逻辑
- 临时在WebSocket消息槽中直接打印原始收到的内容,确认是否有数据到达客户端
- 检查
StompFrame的解析代码是否能正确处理服务器发送的MESSAGE帧,比如是否正确提取headers和payload,是否遗漏帧结束符导致解析失败
验证服务器广播配置
- 确认Spring Boot的STOMP配置中,
/topic/前缀已正确配置广播代理(如@EnableWebSocketMessageBroker中启用simpleBroker("/topic/")),确保广播消息能正确路由到订阅客户端
- 确认Spring Boot的STOMP配置中,
内容的提问来源于stack exchange,提问作者Cristi
相关产品推荐
相关产品推荐

