java后端+前端使用WebSocket實(shí)現(xiàn)消息推送的詳細(xì)流程
前言
在項(xiàng)目的開發(fā)時(shí),遇到實(shí)現(xiàn)服務(wù)器主動(dòng)發(fā)送數(shù)據(jù)到前端頁(yè)面的功能的需求。實(shí)現(xiàn)該功能不外乎使用輪詢和websocket技術(shù),但在考慮到實(shí)時(shí)性和資源損耗后,最后決定使用websocket?,F(xiàn)在就記錄一下用Java實(shí)現(xiàn)Websocket技術(shù)吧~
Java實(shí)現(xiàn)Websocket通常有兩種方式:1、創(chuàng)建WebSocketServer類,里面包含open、close、message、error等方法;2、利用Springboot提供的webSocketHandler類,創(chuàng)建其子類并重寫方法。我們項(xiàng)目雖然使用Springboot框架,不過仍采用了第一種方法實(shí)現(xiàn)。
創(chuàng)建WebSocket的簡(jiǎn)單實(shí)例操作流程
1.引入Websocket依賴
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.7.0</version> </dependency>
2.創(chuàng)建配置類WebSocketConfig
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * 開啟WebSocket支持 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
3.創(chuàng)建WebSocketServer
在websocket協(xié)議下,后端服務(wù)器相當(dāng)于ws里面的客戶端,需要用@ServerEndpoint指定訪問路徑,并使用@Component注入容器
@ServerEndpoint:當(dāng)ServerEndpointExporter類通過Spring配置進(jìn)行聲明并被使用,它將會(huì)去掃描帶有@ServerEndpoint注解的類。被注解的類將被注冊(cè)成為一個(gè)WebSocket端點(diǎn)。所有的配置項(xiàng)都在這個(gè)注解的屬性中
( 如:@ServerEndpoint(“/ws”) )
下面的栗子中@ServerEndpoint指定訪問路徑中包含sid,這個(gè)是用于區(qū)分每個(gè)頁(yè)面
import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.net.Socket; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @ServerEndpoint 注解是一個(gè)類層次的注解,它的功能主要是將目前的類定義成一個(gè)websocket服務(wù)器端, * 注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址,客戶端可以通過這個(gè)URL來連接到WebSocket服務(wù)器端 */ @ServerEndpoint("/notice/{userId}") @Component @Slf4j public class NoticeWebsocket { //記錄連接的客戶端 public static Map<String, Session> clients = new ConcurrentHashMap<>(); /** * userId關(guān)聯(lián)sid(解決同一用戶id,在多個(gè)web端連接的問題) */ public static Map<String, Set<String>> conns = new ConcurrentHashMap<>(); private String sid = null; private String userId; /** * 連接成功后調(diào)用的方法 * @param session * @param userId */ @OnOpen public void onOpen(Session session, @PathParam("userId") String userId) { this.sid = UUID.randomUUID().toString(); this.userId = userId; clients.put(this.sid, session); Set<String> clientSet = conns.get(userId); if (clientSet==null){ clientSet = new HashSet<>(); conns.put(userId,clientSet); } clientSet.add(this.sid); log.info(this.sid + "連接開啟!"); } /** * 連接關(guān)閉調(diào)用的方法 */ @OnClose public void onClose() { log.info(this.sid + "連接斷開!"); clients.remove(this.sid); } /** * 判斷是否連接的方法 * @return */ public static boolean isServerClose() { if (NoticeWebsocket.clients.values().size() == 0) { log.info("已斷開"); return true; }else { log.info("已連接"); return false; } } /** * 發(fā)送給所有用戶 * @param noticeType */ public static void sendMessage(String noticeType){ NoticeWebsocketResp noticeWebsocketResp = new NoticeWebsocketResp(); noticeWebsocketResp.setNoticeType(noticeType); sendMessage(noticeWebsocketResp); } /** * 發(fā)送給所有用戶 * @param noticeWebsocketResp */ public static void sendMessage(NoticeWebsocketResp noticeWebsocketResp){ String message = JSONObject.toJSONString(noticeWebsocketResp); for (Session session1 : NoticeWebsocket.clients.values()) { try { session1.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } } /** * 根據(jù)用戶id發(fā)送給某一個(gè)用戶 * **/ public static void sendMessageByUserId(String userId, NoticeWebsocketResp noticeWebsocketResp) { if (!StringUtils.isEmpty(userId)) { String message = JSONObject.toJSONString(noticeWebsocketResp); Set<String> clientSet = conns.get(userId); if (clientSet != null) { Iterator<String> iterator = clientSet.iterator(); while (iterator.hasNext()) { String sid = iterator.next(); Session session = clients.get(sid); if (session != null) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } } } } } /** * 收到客戶端消息后調(diào)用的方法 * @param message * @param session */ @OnMessage public void onMessage(String message, Session session) { log.info("收到來自窗口"+this.userId+"的信息:"+message); } /** * 發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù) * @param error */ @OnError public void onError(Throwable error) { log.info("錯(cuò)誤"); error.printStackTrace(); } }
封裝了一個(gè)發(fā)送消息的對(duì)象可以直接使用
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @Data @ApiModel("ws通知返回對(duì)象") public class NoticeWebsocketResp<T> { @ApiModelProperty(value = "通知類型") private String noticeType; @ApiModelProperty(value = "通知內(nèi)容") private T noticeInfo; }
4.websocket調(diào)用
一個(gè)用戶調(diào)用接口,主動(dòng)將信息發(fā)給后端,后端接收后再主動(dòng)推送給指定/全部用戶
@RestController @RequestMapping("/order") public class OrderController { @GetMapping("/test") public R test() { NoticeWebsocket.sendMessage("你好,WebSocket"); return R.ok(); } }
前端WebSocket連接
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SseEmitter</title> </head> <body> <div id="message"></div> </body> <script> var limitConnect = 0; init(); function init() { var ws = new WebSocket('ws://192.168.2.88:9060/notice/1'); // 獲取連接狀態(tài) console.log('ws連接狀態(tài):' + ws.readyState); //監(jiān)聽是否連接成功 ws.onopen = function () { console.log('ws連接狀態(tài):' + ws.readyState); limitConnect = 0; //連接成功則發(fā)送一個(gè)數(shù)據(jù) ws.send('我們建立連接啦'); } // 接聽服務(wù)器發(fā)回的信息并處理展示 ws.onmessage = function (data) { console.log('接收到來自服務(wù)器的消息:'); console.log(data); //完成通信后關(guān)閉WebSocket連接 // ws.close(); } // 監(jiān)聽連接關(guān)閉事件 ws.onclose = function () { // 監(jiān)聽整個(gè)過程中websocket的狀態(tài) console.log('ws連接狀態(tài):' + ws.readyState); reconnect(); } // 監(jiān)聽并處理error事件 ws.onerror = function (error) { console.log(error); } } function reconnect() { limitConnect ++; console.log("重連第" + limitConnect + "次"); setTimeout(function(){ init(); },2000); } </script> </html>
項(xiàng)目啟動(dòng),打開頁(yè)面后控制臺(tái)打印連接信息
調(diào)用order/test方法后前端打印推送消息內(nèi)容
這樣,就可以接口或者ws調(diào)用網(wǎng)址的方式進(jìn)行websocket的通信啦~
如果沒有前端頁(yè)面也可以使用在線WebSocket測(cè)試
總結(jié)
到此這篇關(guān)于java后端+前端使用WebSocket實(shí)現(xiàn)消息推送的文章就介紹到這了,更多相關(guān)javaWebSocket實(shí)現(xiàn)消息推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java中使用WebSocket的幾種方式
- Java實(shí)現(xiàn)使用Websocket發(fā)送消息詳細(xì)代碼舉例
- 如何在Java中使用WebSocket協(xié)議
- springboot整合websocket后啟動(dòng)報(bào)錯(cuò)(javax.websocket.server.ServerContainer not available)
- Java WebSocket客戶端接收大量數(shù)據(jù)的三種方案
- 使用Java WebSocket獲取客戶端IP地址的示例代碼
- Java中實(shí)現(xiàn)WebSocket方法詳解
- 教你如何使用Java實(shí)現(xiàn)WebSocket
- 一步步教你如何使用Java實(shí)現(xiàn)WebSocket
- JAVA 日常開發(fā)中Websocket示例詳解
相關(guān)文章
SpringBoot項(xiàng)目里集成Hibernate的示例
在Spring Boot項(xiàng)目中,集成Hibernate可以幫助我們更輕松地進(jìn)行數(shù)據(jù)庫(kù)操作,本文將介紹如何在Spring Boot項(xiàng)目中集成Hibernate,并提供相應(yīng)的示例,感興趣的朋友跟隨小編一起看看吧2023-04-04Java正則驗(yàn)證電話,手機(jī),郵箱,日期,金額的方法示例
這篇文章主要介紹了Java正則驗(yàn)證電話,手機(jī),郵箱,日期,金額的方法,結(jié)合具體實(shí)例形式分析了Java針對(duì)電話,手機(jī),郵箱,日期,金額的正則判定操作技巧,需要的朋友可以參考下2017-03-03深入解析Java的設(shè)計(jì)模式編程中建造者模式的運(yùn)用
這篇文章主要介紹了深入解析Java的設(shè)計(jì)模式編程中建造者模式的運(yùn)用,同時(shí)文中也介紹了建造者模式與工廠模式的區(qū)別,需要的朋友可以參考下2016-02-02Spring MVC傳遞接收參數(shù)方式小結(jié)
大家在開發(fā)中經(jīng)常會(huì)用到Spring MVC Controller來接收請(qǐng)求參數(shù),主要常用的接收方式就是通過實(shí)體對(duì)象以及形參等方式、有些用于GET請(qǐng)求,有些用于POST請(qǐng)求,有些用于兩者,下面介紹幾種常見的Spring MVC傳遞接收參數(shù)的方式2021-11-11Spring Data JPA進(jìn)行數(shù)據(jù)分頁(yè)與排序的方法
這篇文章主要介紹了Spring Data JPA進(jìn)行數(shù)據(jù)分頁(yè)與排序的方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11Java如何通過枚舉實(shí)現(xiàn)有限狀態(tài)機(jī)
這篇文章主要介紹了Java如何通過枚舉實(shí)現(xiàn)有限狀態(tài)機(jī),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07