先说需求
需求很简单,springboot+websocket定时推送前端页面。
因为我之前没有用过websocket,所以在网上找了很多案例,网上给的案例大多都是统计在线人数或者对话框那种,外边的框架差不多,所以我找了一个,这个文章我不贴地址了,直接给的大概是先写一个websocket类,onOpen里面记录数据,然后在写一个controller,用定时器调用onmessage发送数据。
踩坑过程
这个过程代码不难,因为我用的springboot,所以我照猫画虎写了一个@Scheduled,给了一个cron表达式,找了一个网站测试http://www.jsons.cn/websocket/
然后我发现,第一次推送数据的时候好着呢,但是比如我多开页面调接口或者刷新,我刷新几次后台就会给我推几次
原因
后来我发现每一次调用用这个controller的时候都会创造一个定时器,然后一直在定时,所以就会导致这样的情况,你调一次创建一个,这会我脑子里面第一个解决方案就是在onclose销毁的时候把这个定时器干掉
百度了一下,@Scheduled除了该配置文件开关,剩下好像并不是很好干掉,实例化所有bean暴力干掉有点太危险,我想可能是我的代码不对了
最新的解决方案
网上关于websocket的解决方案很多,但是大多都是一个模子,你抄我的我抄你的,后来我就一直在研究这个定时,我想这应该从这里下手,然后我发现每一次刷新都会创建新连接,那我就在这个onopen里面下手,一直到我发现了一个东西,
https://blog.csdn.net/dtlscsl/article/details/94185614
时间轮,具体你们可以看看,然后就开始了我的改造
我直接贴代码,很简单
package com.sbr.pam.websocket;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sbr.pam.mvc.resacc.service.PamResAccService;
import com.sbr.pam.tool.SpringUtils;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @Author:fengqifan
* @DATE: 2022-08-09 16:22
* @Use:
*/
@ServerEndpoint(value = "/webSocket", configurator = LargeScreenWebSocket.class)
//主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
@Component
@EnableScheduling
/*@Component
@EnableScheduling// cron定时任务*/
@Data
public class LargeScreenWebSocket extends ServerEndpointConfig.Configurator {
private static final Logger logger = LoggerFactory.getLogger(LargeScreenWebSocket.class);
private static int onlineCount = 0;
private static Map<String, LargeScreenWebSocket> clients = new ConcurrentHashMap<String, LargeScreenWebSocket>();
private Session session;
private String sessionId;
@OnOpen
public void onOpen(Session session) throws IOException {
this.sessionId = session.getId();
this.session = session;
HashedWheelTimer hashedWheelTimer = SpringUtils.getBean(HashedWheelTimer.class);
addOnlineCount();
clients.put(sessionId, this);
TimerTask timerTask = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
Map<String, Object> resMap = SpringUtils.getBean(PamResAccService.class).getAllInfo();
System.out.println(resMap);
if(clients.containsKey(LargeScreenWebSocket.this.sessionId)){
onMessage(JSON.toJSONString(resMap));
hashedWheelTimer.newTimeout(this, 1, TimeUnit.MINUTES);
}
}
};
hashedWheelTimer.newTimeout(timerTask, 0, TimeUnit.SECONDS);
}
@OnClose
public void onClose() throws IOException {
this.sessionId = session.getId();
clients.remove(sessionId);
subOnlineCount();
}
@OnMessage
public void onMessage(String message) throws IOException {
this.session.getAsyncRemote().sendText(message);
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
public void sendMessageTo(String message, String To) throws IOException {
/* ----- 后续如果需要单独对某一个session推送可以修改这个方法调用发送
this.session.getAsyncRemote().sendText(message);
// session.getBasicRemote().sendText(message);
//session.getAsyncRemote().sendText(message);
for (LargeScreenWebSocket item : clients.values()) {
if (item.sessionId.equals(To)) {
item.session.getAsyncRemote().sendText(message);
}
}*/
}
public void sendMessageAll(String message) throws IOException {
for (LargeScreenWebSocket item : clients.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
public static synchronized void addOnlineCount() {
LargeScreenWebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
LargeScreenWebSocket.onlineCount--;
}
public static synchronized Map<String, LargeScreenWebSocket> getClients() {
return clients;
}
}
创建新链接,初始化我直接给0s,onmessone数据,然后改成我需要的时间间隔,最后删掉那个controller,好了,记录一下这个