1.maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
2.前端代码
<!DOCTYPE HTML>
<html>
<head>
<title>大屏A</title>
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button οnclick="send()">Send</button> <button οnclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
var wsGroupId = "20191009170924631538723061895168";
var type = "screenA"
var url = "ws://localhost:8080/ws/" + wsGroupId + "/" + type;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket(url);
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
// setMessageInnerHTML("error");
console.log(getNowTime() +' 发生异常了');
reconnect(url);
};
//连接成功建立的回调方法
websocket.onopen = function(event){
//setMessageInnerHTML("open");
console.log(getNowTime() +" Socket 已打开");
send("heartbeat");
//心跳检测重置
heartCheck.start()
}
//接收到消息的回调方法
websocket.onmessage = function(event){
//websocket.setMessageInnerHTML(event.data);
// 维持心跳
heartCheck.start();
if(event.data !='heartbeat'){
document.getElementById("text").value=event.data;
console.log(event.data)
}
//window.location.href="http://www.jb51.net";
}
//连接关闭的回调方法
websocket.onclose = function(){
// websocket.setMessageInnerHTML("close");
console.log(getNowTime() +" Socket已关闭");
reconnect(url)
}
// //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
// window.onbeforeunload = function(){
// websocket.close();
// }
//将消息显示在网页上
// function setMessageInnerHTML(innerHTML){
// document.getElementById('message').innerHTML += innerHTML + '<br/>';
// }
//关闭连接
// function closeWebSocket(){
// websocket.close();
// lockReconnect= false;
// reconnect(url);
// }
//发送消息
function send(message){
// var message = document.getElementById('text').value;
websocket.send(message);
}
var lockReconnect = false;//避免重复连接
//重试连接socket
function reconnect(url) {
if(lockReconnect) {
return;
};
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
tt && clearTimeout(tt);
tt = setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 10000);
}
//心跳检测
var heartCheck = {
timeout: 5000,
timeoutObj: null,
serverTimeoutObj: null,
start: function(){
console.log(getNowTime() +" Socket 心跳检测");
var self = this;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
console.log(getNowTime() +' Socket 连接重试');
send("heartbeat");
self.serverTimeoutObj = setTimeout(function() {
console.log(this);
close();
}, self.timeout);
}, this.timeout)
}
}
/**
* 获取系统当前时间
* @returns
*/
function p(s) {
return s < 10 ? '0' + s : s;
}
function getNowTime() {
var myDate = new Date();
//获取当前年
var year = myDate.getFullYear();
//获取当前月
var month = myDate.getMonth() + 1;
//获取当前日
var date = myDate.getDate();
var h = myDate.getHours(); //获取当前小时数(0-23)
var m = myDate.getMinutes(); //获取当前分钟数(0-59)
var s = myDate.getSeconds();
return year + '-' + p(month) + "-" + p(date) + " " + p(h) + ':' + p(m) + ":" + p(s);
}
</script>
</html>
3.后台代码
/** * @author feiyang */ @ServerEndpoint(value = "/ws/{wsGroupId}/{type}") @Component public class WebSocket { private static final Logger logger = LoggerFactory.getLogger(WebSocket.class); private static SessionStorage sessionStorage = SessionStorage.getInstance(); private static Map<String, String> sessionGroup = new ConcurrentHashMap<>(16); /** * 连接建立成功调用的方法 * @param session * @param type * @param wsGroupId * @throws Exception */ @OnOpen public void onOpen(@PathParam("wsGroupId") String wsGroupId, @PathParam("type") String type, Session session) { sessionGroup.put(session.getId(), wsGroupId); sessionStorage.putSession(session, wsGroupId, type); session.setMaxIdleTimeout(3600000); logger.info("websocket连接成功 sessionId:" + session.getId()); logger.info("websocket连接成功 type:" + type); logger.info("websocket连接成功 wsGroupId:" + wsGroupId); logger.info("websocket链接数量:" + sessionGroup.size()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(@PathParam("wsGroupId") String wsGroupId, @PathParam("type") String type, Session session) { logger.info("websocket连接关闭成功 sessionId:" + session.getId()); logger.info(type); logger.info(sessionGroup.get(session.getId())); SessionStorage.getInstance().removeSession(sessionGroup.get(session.getId()), type); } /** * 收到客户端消息后调用的方法 * @param message * @param session */ @OnMessage public void onMessage(@PathParam("wsGroupId") String wsGroupId, @PathParam("type") String type,String message, Session session) { try { logger.info(message); sessionStorage.sendTextSingle( wsGroupId, type, message); } catch (Exception e) { e.printStackTrace(); } } /** * 发生错误时调用 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { logger.info("websocket连接失败 sessionId:" + session.getId()); } }
package com.example.springtest01.socket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.util.StringUtils; import javax.websocket.Session; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * * 作为对外的工具类 * @author zhoukx */ public class SessionStorage { private Map<String, Map<String, Session>> sessionStore = new ConcurrentHashMap<>(); private static volatile SessionStorage storage; private static final Logger logger = LoggerFactory.getLogger(SessionStorage.class); private static final String FLAG_HEART_BEAT = "heartbeat"; private SessionStorage() { } /** * 双端检索机制创建一个 SessionStorage 实例 * @return */ public static synchronized SessionStorage getInstance() { if(storage == null) { synchronized(SessionStorage.class) { if(storage == null) { storage = new SessionStorage(); } } } return storage; } /** * 将ws连接会话分组保存 * @author feiyang * @param session * @param wsGroupId * @param type * @return void * @date 2019/9/19 * @throws */ public void putSession(Session session, String wsGroupId, String type) { if(session != null) { Map<String, Session> sessionGroup = this.sessionStore.get(wsGroupId); if (sessionGroup == null) { sessionGroup = new ConcurrentHashMap<>(16); this.sessionStore.put(wsGroupId, sessionGroup); } sessionGroup.put(type, session); } } /** * ws连接关闭时删除会话 * @author feiyang * @param wsGroupId * @param type * @return void * @date 2019/9/19 * @throws */ public void removeSession(String wsGroupId, String type) { Map<String, Session> sessionGroup = sessionStore.get(wsGroupId); sessionGroup.remove(type); if (sessionGroup.isEmpty()) { this.sessionStore.remove(wsGroupId); } } /** * 获取指定ws连接会话 * @author feiyang * @param wsGroupId * @param type * @return javax.websocket.Session * @date 2019/9/19 * @throws */ public Session getSession(String wsGroupId, String type) { Map<String, Session> sessionGroup = this.sessionStore.get(wsGroupId); return sessionGroup.get(type); } /** * 获取指定ws连接会话组全部session * @author feiyang * @param wsGroupId * @return java.util.List<javax.websocket.Session> * @date 2019/9/19 * @throws */ public List<Session> getSessions(String wsGroupId) { Session[] sessions = new Session[]{}; Map<String, Session> sessionGroup = this.sessionStore.get(wsGroupId); return Arrays.asList(sessionGroup.values().toArray(sessions)); } /** * 获取全部的会话 * @author feiyang * @param * @return java.util.List<javax.websocket.Session> * @date 2019/9/19 * @throws */ public List<Session> getAllSession() { List<Session> sessionList = new ArrayList<>(); for (Map<String, Session> sessionGroup : sessionStore.values()) { Session[] sessions = new Session[]{}; sessionList.addAll(Arrays.asList(sessionGroup.values().toArray(sessions))); } return sessionList; } /** * 针对单个会话发送文本消息 * @author feiyang * @param wsGroupId * @param type * @param text * @return void * @date 2019/9/19 * @throws */ @Async public void sendTextSingle(String wsGroupId, String type, String text){ try { Session session = getSession(wsGroupId, type); if (session.isOpen()) { session.getBasicRemote().sendText(text); if (text.equals(FLAG_HEART_BEAT)) { logger.info("socket维持链接状态:" + text); } else { logger.info("已发送》》》》》" + text); } } } catch (IOException e) { e.printStackTrace(); } } /** * 针对会话组发送文本信息 * @author feiyang * @param wsGroupId * @param text * @return void * @date 2019/9/19 * @throws */ @Async public void sendTextGroup(String wsGroupId, String text) { try { List<Session> sessions = getSessions(wsGroupId); for(Session session : sessions) { if (session.isOpen()) { session.getBasicRemote().sendText(text); logger.info("已发送》》》》》" + text); } } } catch (IOException e) { e.printStackTrace(); } } // /** // * 针对全部会话发送消息 // * @author feiyang // * @param text // * @return void // * @date 2019/9/19 // * @throws // */ // public void sendTextAll(String text) { // try { // List<Session> sessions = getAllSession(); // for(Session session : sessions) { // if (session.isOpen()) { // session.getBasicRemote().sendText(text); // logger.info("已发送》》》》》" + text); // } // } // } catch (IOException e) { // e.printStackTrace(); // } // } }
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter (){ return new ServerEndpointExporter(); } }