Java版WebSocket消息推送系統搭建
最近在做消息推送,網上查了一些資料,開始想的是用MQ來做,後面發現用WebSocket來做的話感覺應該要簡單點,話不多說,準備擼代碼。
後端核心代碼
/**
* 監聽器類:主要任務是用ServletRequest將我們的HttpSession攜帶過去
* @author Monkey
* @date 2020-05-23
*/
@Component
public class RequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
//將所有request請求都攜帶上httpSession
HttpSession httpSession= ((HttpServletRequest) sre.getServletRequest()).getSession();
String uid = sre.getServletRequest().getParameter("uid");
if (!StringUtils.isEmpty(uid)) {
httpSession.setAttribute("uid", uid);
SysContent.setUserLocal(uid);
}
}
/**
* Type: WebSocketConfig
* Description: WebSocket配置類
* @author Monkey
* @date 2020-05-23
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
/**
* Type: WebSocketServer
* Description: WebSocketServer,實現服務器客戶端平等交流,達到服務器可以主動向客戶端發送消息
*
* @author Monkey
* @date 2020-05-23
*/
@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketServer {
//日誌記錄器
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);
//高效,弱一致性,放的是WebSocketServer而非session是爲了複用自身的方法
private static transient volatile Set<WebSocketServer> webSocketSet = ConcurrentHashMap.newKeySet();
private static transient volatile Set<WebSocketServer> tempWebSocketSet = ConcurrentHashMap.newKeySet();
//與某個客戶端的連接會話,需要通過它來給客戶端發送數據
private Session session;
private static transient ConcurrentHashMap<String, Session> map = new ConcurrentHashMap();
/**
* Title: sendInfo
* Description: 羣發消息
* @param message
*/
public static void sendInfo(String message, String sid) throws IOException {
LOGGER.info("webSocket-sendInfo羣發消息:" + message);
RecordLogUtil.info("在線人數:" + getOnlineCount());
if (!StringUtils.isEmpty(sid)) {
Set<Map.Entry<String, Session>> entries = map.entrySet();
for(Map.Entry<String, Session> m : entries){
if (m.getKey().equals(sid)) {
Session s2 = m.getValue();
webSocketSet.forEach(ws -> {
if (ws.session.getId() == s2.getId()) {
ws.sendMessage(message);
return;
}
});
map.remove(m);
break;
}
}
} else {
tempWebSocketSet = ConcurrentHashMap.newKeySet();
for (Map.Entry<String, Session> m : map.entrySet()) {
Session s2 = m.getValue();
webSocketSet.forEach(ws -> {
if (ws.session.getId() == s2.getId()) {
ws.sendMessage(message);
tempWebSocketSet.add(ws);
return;
}
});
}
//過濾完已經掛斷的session
webSocketSet = tempWebSocketSet;
}
}
/**
* Title: getOnlineCount
* Description: 獲取連接數
* @return
*/
public static int getOnlineCount() {
return map.size();
}
/* *********************以下爲非static方法************************** */
/**
* Title: sendMessage
* Description: 向客戶端發送消息
* @param message
* @throws IOException
*/
public boolean sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
return true;
} catch (IOException error) {
LOGGER.error("webSocket-sendMessage發生錯誤:" + error.getClass() + error.getMessage());
return false;
}
}
/**
* 連接建立成功調用的方法*/
@OnOpen
public void onOpen(Session session) throws IOException {
String uid = SysContent.getUserLocal();
RecordLogUtil.info("uid=" + uid);
this.session = session;
if (StringUtils.isEmpty(uid)){
sendMessage("連接失敗");
session.close();
return;
} else {
map.put(uid, this.session);
webSocketSet.add(this); //加入set中
sendMessage("連接成功-" + uid);
RecordLogUtil.info("當前在線人數: " + getOnlineCount());
}
}
/**
* 連接關閉調用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //從set中刪除
//這裏要刪除map裏面對象
for(Map.Entry<String, Session> m : map.entrySet()){
if (m.getValue() == this.session) {
map.remove(m);
RecordLogUtil.info("用戶" + m.getKey() + "已關閉連接!");
break;
}
}
RecordLogUtil.info("在線人數:" + getOnlineCount() + ", 關聯在線人數:" + map.size());
}
/**
* 收到客戶端消息後調用的方法
* @param message 客戶端發送過來的消息*/
@OnMessage
public void onMessage(String message, Session session) {
LOGGER.info("來自客戶端(" + session.getId() + ")的消息:" + message);
sendMessage("Hello, nice to hear you! There are " + webSocketSet.size() + " users like you in total here!");
}
/**
* Title: onError
* Description: 發生錯誤時候回調函數
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
LOGGER.error("webSocket發生錯誤:" + error.getClass() + error.getMessage());
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
}
/**
* 監聽器類:主要任務是用ServletRequest將我們的HttpSession攜帶過去
* @author Monkey
* @date 2020-05-23
*/
public class SysContent {
private static ThreadLocal<HttpServletRequest> requestLocal = new ThreadLocal<HttpServletRequest>();
private static ThreadLocal<HttpServletResponse> responseLocal = new ThreadLocal<HttpServletResponse>();
private static ThreadLocal<String> userLocal = new ThreadLocal<String>();
public static String getUserLocal() {
return userLocal.get();
}
public static void setUserLocal(String userLocal) {
SysContent.userLocal.set(userLocal);
}
public static HttpServletRequest getRequest() {
return (HttpServletRequest) requestLocal.get();
}
public static void setRequest(HttpServletRequest request) {
requestLocal.set(request);
}
public static HttpServletResponse getResponse() {
return (HttpServletResponse) responseLocal.get();
}
public static void setResponse(HttpServletResponse response) {
responseLocal.set(response);
}
public static HttpSession getSession() {
return (HttpSession) ((HttpServletRequest) requestLocal.get()).getSession();
}
}
前端代碼
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>WebSocket測試</title>
<meta charset="utf-8">
<script src="/socket/js/jquery-3.3.1.min.js"></script>
<script src="/socket/js/sockjs.min.js"></script>
</head>
<body>
<!-----start-main---->
<div class="main">
<h2>socketTest</h2>
<input type="button" id="send" value="點擊向服務器發送消息">
<p id="recive"></p>
</div>
<!-----//end-main---->
</body>
<script type="text/javascript">
var ws = null;
var ws_status = false;
function openWebSocket(){
//這裏爲了模擬不同的模擬器。sid是隨機數,開多個瀏覽器窗口的話,就用隨機值測試連接
var sid = Math.random()*10000;
//如果是正式使用,則這個就是綁定用戶的唯一值,一般爲id固定值
//sid = 1;
console.log("sid=" + sid);
//判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
console.log("window...");
ws = new WebSocket("ws://"+window.location.host+"/websocket?uid=" + sid);
} else if ('MozWebSocket' in window) {
console.log("MozWebSocket...");
websocket = new MozWebSocket("ws://"+window.location.host+"/websocket?uid=" + sid);
} else {
console.log("SockJS...");
ws = new SockJS("http://"+window.location.host+"/websocket?uid=" + sid);
}
//這個事件是接受後端傳過來的數據
ws.onmessage = function (event) {
//根據業務邏輯解析數據
console.log("Server:");
console.log(event);
};
ws.onclose = function (event) {
console.log("Connection closed!");
ws_status = false;
};
ws.onopen = function (event){
ws_status = true;
console.log("Connected!");
};
ws.onerror = function (event){
console.log("Connect error!");
};
}
//如果連接失敗,每隔兩秒嘗試重新連接
setInterval(function(){
if(!ws_status){
openWebSocket();
}
}, 2000);
$("#send").click(function(){
ws.send("Hello, server, I am browser.");
$.ajax({
url: "/test/send",
data: {uid: null},
type: "get",
dataType: "json",
success: function(data) {
// data = jQuery.parseJSON(data); //dataType指明瞭返回數據爲json類型,故不需要再反序列化
console.log("開始發送廣播啦!")
}
});
});
</script>
</html>
前端示意圖
demo 測試代碼已經上傳到csdn,喜歡的話可以前往下載。