Nginx代理服务器默认1分钟会导致websocket长连接端口,所以服务端与客户端需要添加心跳检测
1.前端代码Vue
data () {
return {
showDeviceDialog: true,
banner: [],
deviceId: '',
timeout: 55000, // 55秒发一次心跳,比nginx代理端默认连接超时时间(1分钟)稍微小一点,在接近断开的情况下以通信的方式去重置连接时间
serverTimeoutObj: null,
websocket_connected_count: 0, //已尝试连接次数
}
},
watch: {
deviceId (newVal, OldVal) {
if (newVal) {
const routerUrl = this.$router.resolve({
name: 'detail',
params: { id: newVal }
})
window.open(routerUrl.href, '_blank')
}
}
},
components: {
selectDevice
},
methods: {
seeMore () {
const routerUrl = this.$router.resolve({
name: 'list'
})
window.open(routerUrl.href, '_blank')
// this.$router.push({ name: 'list' })
},
confirmSelectDevice (active) {
this.showDeviceDialog = false
this.createWebsocket(active)
},
createWebsocket(active) {
let port;
switch(active) {
case 1:
port = 8092;
break;
case 2:
port = 8093;
break;
case 3:
port = 8094;
break;
}
const clientId = port + '@' + new Date().getTime();
const httpURL = process.env.NODE_ENV === 'production' ? 'wss://henyouliao.cn' : 'ws://192.168.2.135:8005';
this.websocket = new WebSocket(`${httpURL}/websocket/${clientId}`);
// 连接发生错误的回调方法,连接错误时会继续尝试发起连接(尝试5次)
this.websocket.onerror = () => {
console.log("websocket发生了错误" + (this.websocket_connected_count > 0 ? ",已重连" + this.websocket_connected_count + "次" : ""));
if( ++this.websocket_connected_count <= 5) {
this.createWebsocket(active);
}
};
// 连接成功建立的回调方法
this.websocket.onopen = () => {
console.log("websocket已打开(" + new Date().format('yyyy-MM-dd hh:mm:ss') + ")");
//成功建立连接后,重置心跳检测
this.heartCheckReset();
this.heartCheckStart(active);
};
// 接收到消息的回调方法
this.websocket.onmessage = (msg) => {
//如果获取到消息,说明连接是正常的,重置心跳检测
this.heartCheckReset();
this.heartCheckStart(active);
//拿到后端返回的心跳pong说明连接正常
if(msg.data && String(msg.data).toLowerCase().trim() !== "pong") {
console.log('websocket接收到消息', msg.data);
this.deviceId = msg.data
}
};
// 连接关闭的回调方法
this.websocket.onclose = () => {
console.log("websocket已关闭(" + new Date().format('yyyy-MM-dd hh:mm:ss') + ")");
};
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
this.websocket.onbeforeunload = () => {
console.log('监听到窗口关闭事件,主动关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常');
this.websocket.close();
this.heartCheckReset();
};
},
// 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
heartCheckReset() {
clearTimeout(this.serverTimeoutObj);
},
heartCheckStart(active) {
this.serverTimeoutObj = setTimeout(() =>{
if(this.websocket.readyState == 1){
console.log("websocket连接状态,发送心跳检测ping维持心跳连接...");
this.websocket.send("ping");
//如果获取到消息,说明连接是正常的,重置心跳检测
this.heartCheckReset();
this.heartCheckStart(active);
} else {
console.log("websocket断开状态,尝试重连");
this.createWebsocket(active);
}
}, this.timeout)
}
}
2.后台代码SpringBoot
/**
* @author Kisen
* @email [email protected]
* @date 2022/11/16 11:28
* @detail websocket server端
* clientId格式: 机器socket端口@时间戳
* 8092,8093,8094端口分别对应1号机、2号机、3号机
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{clientId}")
public class WebSocketServer {
//用来存放每个客户端对应的WebSocket对象
public static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//当前连接客户端的用户唯一标识
private String clientId;
/**
* 连接建立成功调用的方法
*
* @param session
* @param clientId
*/
@OnOpen
public void onOpen(Session session, @PathParam("clientId") String clientId) {
this.session = session;
this.clientId = clientId;
// clientId是用来表示唯一客户端,如果需要指定发送,需要指定发送通过clientId来区分
webSocketMap.put(clientId, this);
log.info("[WebSocket]客户端{}连接成功,客户端标识:{},当前在线连接数为:{}", session.getId(), clientId, webSocketMap.size());
}
/**
* 连接关闭调用的方法
*
* @param session
*/
@OnClose
public void onClose(Session session) {
webSocketMap.remove(this.clientId);
log.info("[WebSocket]客户端{}连接断开,客户端标识:{},当前在线连接数为:{}", session.getId(), clientId, webSocketMap.size());
}
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到客户端{} 的消息:{}", session.getId(), message);
//心跳检测响应
if (StringUtils.equalsIgnoreCase("ping", message)) {
sendMessage("pong");
log.info("[WebSocket]服务端 已回复客户端{} 的心跳检测: pong", session.getId());
}
}
@OnError
public void one rror(Session session, Throwable error) {
log.error("客户端{} 发生错误:{}", session.getId(), error);
}
/**
* 实现服务器主动推送
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("客户端{} 发生消息({})失败:{}", session.getId(), message, e);
throw new RuntimeException(e);
}
}
}
标签:websocket,log,clientId,心跳,session,后台,连接,客户端
From: https://www.cnblogs.com/yydscn/p/16949577.html