1. 程式人生 > >SpringBoot2+WebSocket之聊天應用實戰(優化版本)

SpringBoot2+WebSocket之聊天應用實戰(優化版本)

背景

之前再SpringBoot2.0整合WebSocket,實現後臺向前端推送資訊中已經進行過一次demo,而這次的demo更加明確,優化了相關程式碼,為IM而生

前提

前提當然是匯入相關的包,以及配置WebSocketConfig.java,請用上篇文章的內容即可。這裡只做優化。

實戰

例如從CopyOnWriteArraySet改為ConcurrentHashMap,保證多執行緒安全同時方便利用map.get(userId)進行推送到指定埠。

相比之前的Set,Set遍歷是費事且麻煩的事情,而Map的get是簡單便捷的,當WebSocket數量大的時候,這個小小的消耗就會聚少成多,影響體驗,所以需要優化。

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.softdev.system.likeu.util.ApiReturnUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import lombok.
extern.slf4j.Slf4j; @ServerEndpoint("/im/{userId}") @Component public class ImController { static Log log=LogFactory.get(ImController.class); //靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。 private static int onlineCount = 0; //舊:concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。 //private static CopyOnWriteArraySet<ImController> webSocketSet = new CopyOnWriteArraySet<ImController>();
//與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 private Session session; //新:使用map物件,便於根據userId來獲取對應的WebSocket private static ConcurrentHashMap<String,ImController> websocketList = new ConcurrentHashMap<>(); //接收sid private String userId=""; /** * 連線建立成功呼叫的方法*/ @OnOpen public void onOpen(Session session,@PathParam("userId") String userId) { this.session = session; websocketList.put(userId,this); log.info("websocketList->"+JSON.toJSONString(websocketList)); //webSocketSet.add(this); //加入set中 addOnlineCount(); //線上數加1 log.info("有新視窗開始監聽:"+userId+",當前線上人數為" + getOnlineCount()); this.userId=userId; try { sendMessage(JSON.toJSONString(ApiReturnUtil.success("連線成功"))); } catch (IOException e) { log.error("websocket IO異常"); } } /** * 連線關閉呼叫的方法 */ @OnClose public void onClose() { if(websocketList.get(this.userId)!=null){ websocketList.remove(this.userId); //webSocketSet.remove(this); //從set中刪除 subOnlineCount(); //線上數減1 log.info("有一連線關閉!當前線上人數為" + getOnlineCount()); } } /** * 收到客戶端訊息後呼叫的方法 * * @param message 客戶端傳送過來的訊息*/ @OnMessage public void onMessage(String message, Session session) { log.info("收到來自視窗"+userId+"的資訊:"+message); if(StringUtils.isNotBlank(message)){ JSONArray list=JSONArray.parseArray(message); for (int i = 0; i < list.size(); i++) { try { //解析傳送的報文 JSONObject object = list.getJSONObject(i); String toUserId=object.getString("toUserId"); String contentText=object.getString("contentText"); object.put("fromUserId",this.userId); //傳送給對應使用者的websocket if(StringUtils.isNotBlank(toUserId)&&StringUtils.isNotBlank(contentText)){ ImController socketx=websocketList.get(toUserId); //需要進行轉換,userId if(socketx!=null){ socketx.sendMessage(JSON.toJSONString(ApiReturnUtil.success(object))); //此處可以放置相關業務程式碼,例如儲存到資料庫 } } }catch (Exception e){ e.printStackTrace(); } } } } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("發生錯誤"); error.printStackTrace(); } /** * 實現伺服器主動推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群發自定義訊息 * */ /*public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException { log.info("推送訊息到視窗"+userId+",推送內容:"+message); for (ImController item : webSocketSet) { try { //這裡可以設定只推送給這個sid的,為null則全部推送 if(userId==null) { item.sendMessage(message); }else if(item.userId.equals(userId)){ item.sendMessage(message); } } catch (IOException e) { continue; } } }*/ public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { ImController.onlineCount++; } public static synchronized void subOnlineCount() { ImController.onlineCount--; } }

網頁

這裡的路徑是寫死的,反正你如果有freemarker最好是根據${request.contextPath}這種動態變數來獲取。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket通訊</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
    var socket;
    function openSocket() {
        if(typeof(WebSocket) == "undefined") {
            console.log("您的瀏覽器不支援WebSocket");
        }else{
            console.log("您的瀏覽器支援WebSocket");
            //實現化WebSocket物件,指定要連線的伺服器地址與埠  建立連線
            //等同於socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
            //var socketUrl="${request.contextPath}/im/"+$("#userId").val();
            var socketUrl="http://localhost:8888/xxxx/im/"+$("#userId").val();
            socketUrl=socketUrl.replace("https","ws").replace("http","ws");
            console.log(socketUrl)
            socket = new WebSocket(socketUrl);
            //開啟事件
            socket.onopen = function() {
                console.log("websocket已開啟");
                //socket.send("這是來自客戶端的訊息" + location.href + new Date());
            };
            //獲得訊息事件
            socket.onmessage = function(msg) {
                console.log(msg.data);
                //發現訊息進入    開始處理前端觸發邏輯
            };
            //關閉事件
            socket.onclose = function() {
                console.log("websocket已關閉");
            };
            //發生了錯誤事件
            socket.onerror = function() {
                console.log("websocket發生了錯誤");
            }
        }
    }
    function sendMessage() {
        if(typeof(WebSocket) == "undefined") {
            console.log("您的瀏覽器不支援WebSocket");
        }else {
            console.log("您的瀏覽器支援WebSocket");
            console.log('[{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}]');
            socket.send('[{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}]');
        }
    }
</script>
<body>
    <p>【userId】:<div><input id="userId" name="userId" type="text" value="25"></div>
    <p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="26"></div>
    <p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="嗷嗷嗷"></div>
    <p>【操作】:<div><a onclick="openSocket()">開啟socket</a></div>
    <p>【操作】:<div><a onclick="sendMessage()">傳送訊息</a></div>
</body>

</html>

效果