springboot+websocket双向通信实现消息推送功能
项目中如果需要假如消息推送功能,有时会用到websocket,这是一种长连接方式与服务器进行连接,优点:实时性较高,如果无数据更新时,并不会频繁进行请求,而只要数据进行更新,那么服务器就会想客户端发送请求,而这样的方式是以服务器资源作为代价来保证实时性。
前端代码
//webSocket对象
var websocket = null;
var userId;
//避免重复连接
var lockReconnect = false, tt;
createWebSocket();
/**
* webSocket重连
*/
function reconnect() {
if (lockReconnect) {
return;
}
lockReconnect = true;
tt && clearTimeout(tt);
tt = setTimeout(function () {
console.log('重连中...');
lockReconnect = false;
createWebSocket();
}, 4000);
}
/**
* websocket心跳检测
*/
var heartCheck = {
timeout: 3000,
timeoutObj: null,
serverTimeoutObj: null,
reset: function () {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function () {
var self = this;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function () {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
websocket.send("connect");
self.serverTimeoutObj = setTimeout(function () {
}, self.timeout)
}, this.timeout)
}
};
function createWebSocket() {
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
var pathName = window.document.location.pathname;
var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
//动态获取websocket服务地址
var url = "ws://" + window.document.location.host + projectName + "/webSocket/" + userId;
// 浏览器支持Websocket
websocket = new WebSocket(url);
//WebSocket连接发生错误的回调方法
websocket.onerror = function () {
reconnect();
};
//WebSocket连接成功建立的回调方法
websocket.onopen = function () {
//userId是用户的id也可以是token,按需设置,只要是唯一用户标识
websocket.send('{"toUserId":"' + userId + '"}')
//心跳检测重置
heartCheck.reset().start();
};
//接收到消息的回调方法
websocket.onmessage = function (event) {
//这里的event则是服务器所发送过来的消息
console.log('消息内容:'+event);
heartCheck.reset().start();
};
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
};
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//连接关闭的回调方法
websocket.onclose = function () {
heartCheck.reset();//心跳检测
reconnect();
};
} else {
console.log("您的浏览器暂不支持webSocket");
}
}
后端代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint(value = "/webSocket/{userId}", configurator = MySpringConfigurator.class)
public class WebSocket {
/**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
private static ConcurrentHashMap<String,WebSocket> webSocketMap = new ConcurrentHashMap<>();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
/**接收userId*/
private String userId="";
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") String userId) {
this.session = session;
this.userId=userId;
if(webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
webSocketMap.put(userId,this);
}else{
webSocketMap.put(userId,this);
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
//收到消息的内容可以自定义
}
/**
* 连接失败调用的方法
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
/**
* 服务器主动推送消息
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 向用户发送自定义消息
* */
public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
if(webSocketMap.containsKey(userId)){
webSocketMap.get(userId).sendMessage(message);
}else{
}
}
}
这里有个需要注意的地方,就是webSocket在连接一定时间后未收到服务器消息时会自动断开连接,那么则需要假如心跳检测机制。