筆者是需要實現指定用戶獲得實時數據,類似好友邀請助力當前組隊情況
spring就有很好的封裝,上代碼;
引入pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.1.3.RELEASE</version> </dependency>
注入bean,WebSocketConfig.java
package com.myelephant.projects.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * @Author: StephenZhang * @date: 2021-01-19 17:54 */ @Configuration public class WebSocketConfig { /** * 給spring容器注入這個ServerEndpointExporter對象 * 相當於xml: * <beans> * <bean id="serverEndpointExporter" class="org.springframework.web.socket.server.standard.ServerEndpointExporter"/> * </beans> * <p> * 檢測所有帶有@serverEndpoint注解的bean並注冊他們。 * * @return */ @Bean public ServerEndpointExporter serverEndpointExporter() { System.out.println("我被注入了"); return new ServerEndpointExporter(); } }
自定義一個session綁定對象,相當於指定多個用戶接收消息
Clent.java
bagId 組隊Id
session 用戶會話 唯一
package com.myelephant.projects.websocket; import com.dandandog.framework.mapstruct.model.MapperVo; import lombok.Data; import lombok.EqualsAndHashCode; import javax.websocket.Session; import java.io.Serializable; /** * @Author: StephenZhang * @date: 2021-01-21 16:01 */ @Data @EqualsAndHashCode(callSuper = true) public class ClientVo extends MapperVo implements Serializable { private String bagId; private Session session; }
邏輯實現
目的客服端攜帶bagId 則生成對象同時拿到對應session
add,如有兩個用戶同一個bagId則指定用戶獲取數據即可
通過bagId相等的去發送消息,調用
sendMessage()
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** * @Author: StephenZhang * @date: 2021-01-19 17:55 */ @ServerEndpoint(value = "/ws/test/{bagId}") @Component public class WebSocketServer { private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class); private static final AtomicInteger OnlineCount = new AtomicInteger(0); /** * 用線程安全的CopyOnWriteArraySet來存放客戶端連接的信息 */ private static CopyOnWriteArraySet<ClientVo> socketServers = new CopyOnWriteArraySet<>(); /** * websocket封裝的session,信息推送,就是通過它來信息推送 */ private Session session; /** * 服務端的userName,因為用的是set,每個客戶端的username必須不一樣,否則會被覆蓋。 * 要想完成ui界面聊天的功能,服務端也需要作為客戶端來接收后台推送用戶發送的信息 */ private final static String SYS_USERNAME = "coding"; /** * 用戶連接時觸發,我們將其添加到 * 保存客戶端連接信息的socketServers中 * * @param session * @param bagId */ @OnOpen public void open(Session session, @PathParam(value = "bagId") String bagId) { this.session = session; ClientVo clientVo = new ClientVo(); clientVo.setBagId(bagId); clientVo.setSession(session); socketServers.add(clientVo); logger.info("客戶端:【{}】連接成功", bagId); logger.info("有連接成功,當前連接數為:{}", socketServers.size()); } /** * 收到客戶端發送信息時觸發 * 我們將其推送給客戶端(coding) * 其實也就是服務端本身,為了達到前端聊天效果才這么做的 * * @param message */ @OnMessage public void onMessage(String message) { ClientVo client = socketServers.stream().filter(cli -> cli.getSession() == session) .collect(Collectors.toList()).get(0); sendMessage(client.getBagId() + "<--" + message, SYS_USERNAME); logger.info("客戶端:【{}】發送信息:{}", client.getBagId(), message); } /** * 連接關閉觸發,通過sessionId來移除 * socketServers中客戶端連接信息 */ @OnClose public void onClose() { socketServers.forEach(client -> { if (client.getSession().getId().equals(session.getId())) { logger.info("客戶端:【{}】斷開連接", client.getBagId()); socketServers.remove(client); logger.info("有連接關閉,當前連接數為:{}", socketServers.size()); } }); } /** * 發生錯誤時觸發 * * @param error */ @OnError public void onError(Throwable error) { socketServers.forEach(client -> { if (client.getSession().getId().equals(session.getId())) { socketServers.remove(client); logger.error("客戶端:【{}】發生異常", client.getBagId()); error.printStackTrace(); } }); } /** * 信息發送的方法,通過客戶端的bagId * 拿到其對應的session,調用信息推送的方法 * * @param message * @param bagId */ public synchronized static void sendMessage(String message, String bagId) { socketServers.forEach(client -> { if (bagId.equals(client.getBagId())) { try { client.getSession().getBasicRemote().sendText(message); logger.info("服務端推送給客戶端 :【{}】", client.getBagId(), message); } catch (IOException e) { e.printStackTrace(); } } }); } /** * 獲取服務端當前客戶端的連接數量, * 因為服務端本身也作為客戶端接受信息, * 所以連接總數還要減去服務端 * 本身的一個連接數 * <p> * 這里運用三元運算符是因為客戶端第一次在加載的時候 * 客戶端本身也沒有進行連接,-1 就會出現總數為-1的情況, * 這里主要就是為了避免出現連接數為-1的情況 * * @return */ public synchronized static int getOnlineNum() { return socketServers.stream().filter(client -> !client.getBagId().equals(SYS_USERNAME)) .collect(Collectors.toList()).size(); } /** * 獲取在線用戶名,前端界面需要用到 * * @return */ public synchronized static List<String> getOnlineUsers() { List<String> onlineUsers = socketServers.stream() .filter(client -> !client.getBagId().equals(SYS_USERNAME)) .map(client -> client.getBagId()) .collect(Collectors.toList()); return onlineUsers; } /** * 信息群發,我們要排除服務端自己不接收到推送信息 * 所以我們在發送的時候將服務端排除掉 * * @param message */ public synchronized static void sendAll(String message) { //群發,不能發送給服務端自己 socketServers.stream().filter(cli -> cli.getBagId() != SYS_USERNAME) .forEach(client -> { try { client.getSession().getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } }); logger.info("服務端推送給所有客戶端 :【{}】", message); } /** * 多個人發送給指定的幾個用戶 * * @param message * @param persons */ public synchronized static void SendMany(String message, String[] persons) { for (String userName : persons) { sendMessage(message, userName); } } }
貼上前端代碼
<html> <head> <meta charset="UTF-8"> <title>websocket測試</title> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <style type="text/css"> h3,h4{ text-align:center; } </style> </head> <body> <h3>WebSocket測試,客戶端接收到的消息如下:</h3> <textarea id = "messageId" readonly="readonly" cols="150" rows="30" > </textarea> <script type="text/javascript"> var socket; if (typeof (WebSocket) == "undefined") { console.log("遺憾:您的瀏覽器不支持WebSocket"); } else { console.log("恭喜:您的瀏覽器支持WebSocket"); //實現化WebSocket對象 //指定要連接的服務器地址與端口建立連接 //注意ws、wss使用不同的端口。我使用自簽名的證書測試, //無法使用wss,瀏覽器打開WebSocket時報錯 //ws對應http、wss對應https。 if(!socket){ socket = new WebSocket("ws://localhost:8080/api/ws/test/96325"); } //連接打開事件 socket.onopen = function() { console.log("Socket 已打開"); socket.send("消息發送測試(From Client)"); }; //收到消息事件 socket.onmessage = function(msg) { $("#messageId").append(msg.data+ "\n"); console.log(msg.data ); //msg = success則更新 分頁獲取禮包人數信息and分頁獲取當前團隊人數信息 }; //連接關閉事件 socket.onclose = function() { console.log("Socket已關閉"); }; //發生了錯誤事件 socket.onerror = function() { alert("Socket發生了錯誤"); } //窗口關閉時,關閉連接 window.unload=function() { socket.close(); }; } </script> </body> </html>