1、先引pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、建立一个websocketconfig配置类
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3、建立一个websocketservice处理类
@ServerEndpoint("/imserver/{userName}")
@Component
public class WebSocketServer {
static Log log=LogFactory.get(WebSocketServer.class);
/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
private static int onlineCount = 0;
/**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
/**接收userId*/
private String userName="";
public static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
// 心跳尝试次数
private AtomicInteger heartbeatAttempts;
private static final int MAX_HEARTBEAT_ATTEMPTS = 3;
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session,@PathParam("userName") String userName) {
this.session = session;
this.userName=userName;
if(webSocketMap.containsKey(userName)){
webSocketMap.remove(userName);
webSocketMap.put(userName,this);
//加入set中
}else{
webSocketMap.put(userName,this);
//加入set中
addOnlineCount();
//在线数加1
}
heartbeatAttempts = new AtomicInteger(0);
log.info("用户连接:"+userName+",当前在线人数为:" + getOnlineCount());
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("用户:"+userName+",网络异常!!!!!!");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(webSocketMap.containsKey(userName)){
webSocketMap.remove(userName);
//从set中删除
subOnlineCount();
}
log.info("用户退出:"+userName+",当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:"+userName+",报文:"+message);
//可以群发消息
//消息保存到数据库、redis
if(StringUtils.isNotBlank(message)){
try {
//解析发送的报文
// JSONObject jsonObject = JSON.parseObject(message);
//追加发送人(防止串改)
// jsonObject.put("fromUserId",this.userName);
// String toUserId=jsonObject.getString("toUserId");
//传送给对应toUserId用户的websocket
if(StringUtils.isNotBlank(userName)&&webSocketMap.containsKey(userName)){
webSocketMap.get(userName).sendMessage("fromUserId");
this.heartbeatAttempts.set(0);
}else{
log.error("请求的userName:"+userName+"不在该服务器上");
//否则不在这个服务器上,发送到mysql或者redis
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:"+this.userName+",原因:"+error.getMessage());
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 发送自定义消息
* */
public static void sendInfo(String message,@PathParam("userName") String userName) throws IOException {
log.info("发送消息到:"+userName+",报文:"+message);
if(StringUtils.isNotBlank(userName)&&webSocketMap.containsKey(userName)){
webSocketMap.get(userName).sendMessage(message);
}else{
log.error("用户"+userName+",不在线!");
}
}
@Scheduled(fixedDelay = 10000)
public void sendHeartBeat() {
CopyOnWriteArraySet<WebSocketServer> webSocketSet;
try {
webSocketSet = WebSocketServer.getWebSocketSet();
log.info("连接数量:" + webSocketSet.size());
if(webSocketSet.size() == 0){
return;
}
log.info("定时发送心跳");
// 遍历所有连接,发送心跳
webSocketSet.forEach(obj -> {
Session session = obj.getSession();
log.info("sessionId:" + session.getId() +" 心跳ping发送次数:" + obj.getHeartbeatAttempts().get());
if(obj.getHeartbeatAttempts().get() >= MAX_HEARTBEAT_ATTEMPTS) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
log.error("session close error:" + e.getMessage());
}
} else {
obj.getHeartbeatAttempts().incrementAndGet();
if (session.isOpen()) {
session.getAsyncRemote().sendText("ping");
}
}
});
}catch (Exception e){
log.error("发送心跳 error:" + e.getMessage());
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
public static void setWebSocketSet(CopyOnWriteArraySet<WebSocketServer> webSocketSet) {
WebSocketServer.webSocketSet = webSocketSet;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public AtomicInteger getHeartbeatAttempts() {
return heartbeatAttempts;
}
public void setHeartbeatAttempts(AtomicInteger heartbeatAttempts) {
this.heartbeatAttempts = heartbeatAttempts;
}
}
4、vue
data() {
return {
webSocket: null,
lockReconnect: false,
timeout: 20 * 1000,
timeoutObj: null,
serverTimeoutObj: null,
timeoutnum: null
}
},
mounted() {
this.initSocket()
},
methods: {
open2(txt) {
this.$notify({
title: '提示',
message: txt,
duration: 0
})
},
initSocket() {
this.webSocket = new WebSocket(ip地址+接口参数)
this.webSocket.onopen = this.webSocketOnOpen
this.webSocket.onclose = this.webSocketOnClose
this.webSocket.onmessage = this.webSocketOnMessage
this.webSocket.onerror = this.webSocketOnError
},
// 重新连接
reconnect() {
var that = this
if (that.lockReconnect) {
return
}
that.lockReconnect = true
// 没连接上会一直重连,设置延迟避免请求过多
that.timeoutnum && clearTimeout(that.timeoutnum)
that.timeoutnum = setTimeout(function() {
// 新连接
that.initSocket()
that.lockReconnect = false
}, 5000)
},
// 重置心跳
reset() {
var that = this
// 清除时间
clearTimeout(that.timeoutObj)
clearTimeout(that.serverTimeoutObj)
// 重启心跳
that.start()
},
// 开启心跳
start() {
var self = this
self.timeoutObj && clearTimeout(self.timeoutObj)
self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj)
self.timeoutObj = setTimeout(function() {
// 这里发送一个心跳,后端收到后,返回一个心跳消息
if (self.webSocket.readyState === 1) {
// 如果连接正常
self.webSocket.send('heartbeat')
} else {
// 否则重连
self.reconnect()
console.log('否则重连')
}
self.serverTimeoutObj = setTimeout(function() {
// 超时关闭
self.webSocket.close()
}, self.timeout)
}, self.timeout)
},
// 建立连接成功后的状态
webSocketOnOpen() {
console.log('websocket连接成功')
// 开启心跳
this.start()
},
// 获取到后台消息的事件,操作数据的代码在onmessage中书写
webSocketOnMessage(res) {
// res就是后台实时传过来的数据
console.log(res)
if (res.data === 'fromUserId') {
console.log(res)
} else {
this.open2(res.data)
}
// 收到服务器信息,心跳重置
this.reset()
},
// 关闭连接
webSocketOnClose() {
this.webSocket.close()
console.log('websocket连接已关闭')
// 重连
this.reconnect()
},
// 连接失败的事件
webSocketOnError(res) {
console.log('websocket连接失败')
// 打印失败的数据
console.log(res)
// 重连
this.reconnect()
},
},