问题描述
所见即所得,上图片。浏览器为chrome
使用stomp-js做客户端,spring框架的stomp做服务端。建立了连接,然后订阅了一个topic。过了一段时间后,client端收到了server端发来的消息:Error message:Session closed(连接被关闭了)。
console里打印日志:Connection closed to ws://xx.xx.xx
代码
不贴代码就巴拉巴拉的那些小魔仙们,不觉得自己在耍流氓么?
js代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ActiveMq推送:消费端.</title>
<!--<link rel="stylesheet" type="text/css" href="default.css">-->
<!--<link rel="stylesheet" type="text/css" href="jquery.notify.jcss">-->
<script type="text/javascript" src="stomp.umd.js"></script>
<script type="text/javascript" src="jquery-3.7.1.min.js"></script>
</head>
<script type="text/javascript">
$(document).ready(function () {
//创建客户端
const client = new StompJs.Client({
brokerURL: 'ws://localhost:8080/websocket/caltta-cm/',
connectHeaders: {
login: ' ',
passcode: ' ',
},
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 5000,
heartbeatOutgoing: 5000,
});
var connect = function (frame) {
//订阅主题的消息
client.subscribe("/topic/cm_ne_status_topic", function (message) {
//弹出业务消息提醒
if (message.body) {
console.log("got message with body " + message.body)
} else {
alert("got empty message");
}
});
};
client.onConnect = connect;
client.onStompError = function (frame) {
// Will be invoked in case of error encountered at Broker
// Bad login/passcode typically will cause an error
// Complaint brokers will set `message` header with a brief message. Body may contain details.
// Compliant brokers will terminate the connection after any error
console.log('Broker reported error: ' + frame.headers['message']);
console.log('Additional details: ' + frame.body);
};
client.activate();
});
</script>
<body>
<div id="body"></div>
</body>
</html>
java代码:
@Configuration
@EnableWebSocketMessageBroker
public class BrokerWebSocketConfig implements WebSocketMessageBrokerConfigurer {
public static final String SUBSCRIBE_PATH = "/websocket/";
public static final String TOPIC_PATH = "/topic/";
private TaskScheduler messageBrokerTaskScheduler;
@Value("${spring.application.name}")
private String applicationName;
@Autowired
public void setMessageBrokerTaskScheduler(@Lazy TaskScheduler taskScheduler) {
this.messageBrokerTaskScheduler = taskScheduler;
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint(SUBSCRIBE_PATH + applicationName + "/").setAllowedOrigins("*");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker(TOPIC_PATH)
.setHeartbeatValue(new long[]{5000, 5000})
.setTaskScheduler(this.messageBrokerTaskScheduler);
}
}
问题分析
this._pinger = setInterval(() => {
console.log("***发送心跳前,判断状态StompSocketState。***")
if (this._webSocket.readyState === exports.StompSocketState.OPEN) {
this._webSocket.send(BYTE.LF);
this.debug('>>> PING');
}else{
console.log("this._webSocket.readyState状态关闭了,所以没有发送心跳(PING)")
}
}, ttl);
上面是stomp.js发送心跳的定时器,我设置的是5秒发送一次。但是,从截图中发现,client端并没有按时发送心跳给server端,导致server端认定client端挂掉了(server端判断client端挂掉的逻辑在SimpleBrokerMessageHandler类中,我这个例子中就是超过5*3=15秒不发送心跳就判定client挂了),所以断开了连接。追根溯源,client端不发送心跳给server端的原因是什么?答案就是计时器没有按时执行,这篇帖子里给出了答案JS定时器执行不可靠的原因及解决方案
解决方案1(上厕)
计时器没有按时间执行,因为可能当时在忙着上厕所,谁还没有个急事呢。解决这个问题很简单,把心跳检测的时间参数(heartbeatIncoming和heartbeatOutgoing)设定的大一些就行了,等计时器上完厕所再发心跳给server端。
const client = new StompJs.Client({
brokerURL: 'ws://localhost:8080/websocket/caltta-cm/',
connectHeaders: {
login: ' ',
passcode: ' ',
},
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 120000,
heartbeatOutgoing: 120000,
});
解决方案2(下厕)
修改stomp.js里发送心跳的定时器,换成webworker(代码自行百度)。这种方法的缺点就是麻烦。优点是心跳检测可以精确到秒,而不会报错。
this._pinger = window["reliableSetInterval"](() => {
if (this._webSocket.readyState === exports.StompSocketState.OPEN) {
this._webSocket.send(BYTE.LF);
//this.debug('>>> PING');
}
}, ttl);
补充说明
即使改了参数,难保不挂掉。stomp帮你加了重新机制,但重连接后还需要重新订阅topic哦,切记!
了解原理很重要,附上stomp协议。
网友评论