SpringBoot2+WebSocket之聊天應用實戰(優化版本)
阿新 • • 發佈:2019-01-09
背景
之前再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>
效果