Java版WebSocket消息推送系統搭建

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,喜歡的話可以前往下載。

https://download.csdn.net/download/lj88811498/12453985

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章