<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在專案的開發時,遇到實現伺服器主動傳送資料到前端頁面的功能的需求。實現該功能不外乎使用輪詢和websocket技術,但在考慮到實時性和資源損耗後,最後決定使用websocket。現在就記錄一下用Java實現Websocket技術吧~
Java實現Websocket通常有兩種方式:1、建立WebSocketServer類,裡面包含open、close、message、error等方法;2、利用Springboot提供的webSocketHandler類,建立其子類並重寫方法。我們專案雖然使用Springboot框架,不過仍採用了第一種方法實現。
<!-- 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>
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(); } }
在websocket協定下,後端伺服器相當於ws裡面的使用者端,需要用@ServerEndpoint指定存取路徑,並使用@Component注入容器
@ServerEndpoint:當ServerEndpointExporter類通過Spring設定進行宣告並被使用,它將會去掃描帶有@ServerEndpoint註解的類。被註解的類將被註冊成為一個WebSocket端點。所有的設定項都在這個註解的屬性中
( 如:@ServerEndpoint(“/ws”) )
下面的栗子中@ServerEndpoint指定存取路徑中包含sid,這個是用於區分每個頁面
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 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket伺服器端, * 註解的值將被用於監聽使用者連線的終端存取URL地址,使用者端可以通過這個URL來連線到WebSocket伺服器端 */ @ServerEndpoint("/notice/{userId}") @Component @Slf4j public class NoticeWebsocket { //記錄連線的使用者端 public static Map<String, Session> clients = new ConcurrentHashMap<>(); /** * userId關聯sid(解決同一使用者id,在多個web端連線的問題) */ public static Map<String, Set<String>> conns = new ConcurrentHashMap<>(); private String sid = null; private String userId; /** * 連線成功後呼叫的方法 * @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 + "連線開啟!"); } /** * 連線關閉呼叫的方法 */ @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; } } /** * 傳送給所有使用者 * @param noticeType */ public static void sendMessage(String noticeType){ NoticeWebsocketResp noticeWebsocketResp = new NoticeWebsocketResp(); noticeWebsocketResp.setNoticeType(noticeType); sendMessage(noticeWebsocketResp); } /** * 傳送給所有使用者 * @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(); } } } /** * 根據使用者id傳送給某一個使用者 * **/ 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(); } } } } } } /** * 收到使用者端訊息後呼叫的方法 * @param message * @param session */ @OnMessage public void onMessage(String message, Session session) { log.info("收到來自視窗"+this.userId+"的資訊:"+message); } /** * 發生錯誤時的回撥函數 * @param error */ @OnError public void onError(Throwable error) { log.info("錯誤"); error.printStackTrace(); } }
封裝了一個傳送訊息的物件可以直接使用
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @Data @ApiModel("ws通知返回物件") public class NoticeWebsocketResp<T> { @ApiModelProperty(value = "通知型別") private String noticeType; @ApiModelProperty(value = "通知內容") private T noticeInfo; }
一個使用者呼叫介面,主動將資訊發給後端,後端接收後再主動推播給指定/全部使用者
@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'); // 獲取連線狀態 console.log('ws連線狀態:' + ws.readyState); //監聽是否連線成功 ws.onopen = function () { console.log('ws連線狀態:' + ws.readyState); limitConnect = 0; //連線成功則傳送一個資料 ws.send('我們建立連線啦'); } // 接聽伺服器發回的資訊並處理展示 ws.onmessage = function (data) { console.log('接收到來自伺服器的訊息:'); console.log(data); //完成通訊後關閉WebSocket連線 // ws.close(); } // 監聽連線關閉事件 ws.onclose = function () { // 監聽整個過程中websocket的狀態 console.log('ws連線狀態:' + ws.readyState); reconnect(); } // 監聽並處理error事件 ws.onerror = function (error) { console.log(error); } } function reconnect() { limitConnect ++; console.log("重連第" + limitConnect + "次"); setTimeout(function(){ init(); },2000); } </script> </html>
專案啟動,開啟頁面後控制檯列印連線資訊
呼叫order/test方法後前端列印推播訊息內容
這樣,就可以介面或者ws呼叫網址的方式進行websocket的通訊啦~
如果沒有前端頁面也可以使用線上WebSocket測試
到此這篇關於java後端+前端使用WebSocket實現訊息推播的文章就介紹到這了,更多相關javaWebSocket實現訊息推播內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45