小夥伴們,週一快樂。悄悄告訴你們一個好消息,還有四天就放假了。。驚不驚喜 意不意外javascript
今天大Boss找我,小優呀,給你一個需求:用戶在app填寫完信息而後推送給管理員Pc端。 我問Boss就這麼簡單?Boos說就是這麼簡單,你要考慮程序性能,推送的數據準確性。html
忽然腦子復現了兩個思路方式:java
A:app和Pc公用一個數據庫,前段設置一個定時,每一秒輪循查詢。程序員
B:在pc加一個刷新按鈕,一直人工刷新。。洗刷洗刷~web
我就去和大Boss溝通方案,他說兩個都不行。回去從新想方案。spring
太難了,想破腦子也想不到啊,算了對於我這種面向百度開發的高級程序員來講。先問問度娘吧。數據庫
度娘告訴我好多種方式:apache
1.Ajax輪循瀏覽器
優勢:客戶端很容易實現良好的錯誤處理系統和超時管理,實現成本與Ajax輪詢的方式相似。安全
缺點:須要服務器端有特殊的功能來臨時掛起鏈接。當客戶端發起的鏈接較多時,服務器端會長期保持多個鏈接,具備必定的風險。
優勢: 實時性好(消息延時小);性能好(能支持大量用戶)
缺點: 長期佔用鏈接,喪失了無狀態高併發的特色。
優勢:
一、 較少的控制開銷。在鏈接建立後,服務器和客戶端之間交換數據時,用於協議控制的數據包頭部相對較小。在不包含擴展的狀況下,對於服務器到客戶端的內容,此頭部大小隻有2至10字節(和數據包長度有關);對於客戶端到服務器的內容,此頭部還須要加上額外的4字節的掩碼。相對於HTTP請求每次都要攜帶完整的頭部,此項開銷顯著減小了。 二、更強的實時性。因爲協議是全雙工的,因此服務器能夠隨時主動給客戶端下發數據。相對於HTTP請求須要等待客戶端發起請求服務端才能響應,延遲明顯更少;即便是和Comet等相似的長輪詢比較,其也能在短期內更屢次地傳遞數據。 三、保持鏈接狀態。與HTTP不一樣的是,Websocket須要先建立鏈接,這就使得其成爲一種有狀態的協議,以後通訊時能夠省略部分狀態信息。而HTTP請求可能須要在每一個請求都攜帶狀態信息(如身份認證等)。 四、更好的二進制支持。Websocket定義了二進制幀,相對HTTP,能夠更輕鬆地處理二進制內容。 五、能夠支持擴展。Websocket定義了擴展,用戶能夠擴展協議、實現部分自定義的子協議。如部分瀏覽器支持壓縮等。 六、更好的壓縮效果。相對於HTTP壓縮,Websocket在適當的擴展支持下,能夠沿用以前內容的上下文,在傳遞相似的數據時,能夠顯著地提升壓縮率。
缺點:不支持低版本的IE瀏覽器
經老夫掐指一算,小優確定會選擇webSocket
今天就和你們一塊兒學習SpringBoot整合webSocket 一對一發送消息,一對多發送消息,服務器主動推送消息。
什麼是webSocket?
對於上面的小優的業務,我給你們畫一個牛成圖。hiahia~~ 能夠參考看看哈。
不知道你們能不能看懂。再給你們寫一個word版本的流程圖:
好了,廢話少說如今SpringBoot和WebSocket集成 上代碼:
①、工程目錄:
②、pom文件:
pom.xml文件: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.cnbuilder</groupId> <artifactId>websocket</artifactId> <version>0.0.1-SNAPSHOT</version> <name>websocket</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--SpringBootWeb包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--webSocketjar--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!--訪問接口跳轉templates目錄下的html 必須加--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <!--打包--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
③、webSocket配置文件:
WebSocketConfig: package cn.cnbuilder.websocket.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
④、websocket鏈接信息配置:
ProductWebSocket: package cn.cnbuilder.websocket; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; /** * @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端, * 註解的值將被用於監聽用戶鏈接的終端訪問URL地址,客戶端能夠經過這個URL來鏈接到WebSocket服務器端 * @ServerEndpoint 能夠把當前類變成websocket服務類 */ @ServerEndpoint("/websocket/{userId}") @Component public class ProductWebSocket { //靜態變量,用來記錄當前在線鏈接數。應該把它設計成線程安全的。 private static int onlineCount = 0; //concurrent包的線程安全Set,用來存放每一個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通訊的話,可使用Map來存放,其中Key能夠爲用戶id private static ConcurrentHashMap<String, ProductWebSocket> webSocketSet = new ConcurrentHashMap<String, ProductWebSocket>(); //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 private Session session; //當前發消息的人員編號 private String userId = ""; /** * 線程安全的統計在線人數 * * @return */ public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { ProductWebSocket.onlineCount++; } public static synchronized void subOnlineCount() { ProductWebSocket.onlineCount--; } /** * 鏈接創建成功調用的方法 * * @param param 用戶惟一標示 * @param session 可選的參數。session爲與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 */ @OnOpen public void onOpen(@PathParam(value = "userId") String param, Session session) { userId = param;//接收到發送消息的人員編號 this.session = session; webSocketSet.put(param, this);//加入線程安全map中 addOnlineCount(); //在線數加1 System.out.println("用戶id:" + param + "加入鏈接!當前在線人數爲" + getOnlineCount()); } /** * 鏈接關閉調用的方法 */ @OnClose public void onClose() { if (!userId.equals("")) { webSocketSet.remove(userId); //根據用戶id從ma中刪除 subOnlineCount(); //在線數減1 System.out.println("用戶id:" + userId + "關閉鏈接!當前在線人數爲" + getOnlineCount()); } } /** * 收到客戶端消息後調用的方法 * * @param message 客戶端發送過來的消息 * @param session 可選的參數 */ @OnMessage public void onMessage(String message, Session session) { System.out.println("來自客戶端的消息:" + message); //要發送人的用戶uuid String sendUserId = message.split(",")[1]; //發送的信息 String sendMessage = message.split(",")[0]; //給指定的人發消息 sendToUser(sendUserId, sendMessage); } /** * 給指定的人發送消息 * * @param message */ public void sendToUser(String sendUserId, String message) { try { if (webSocketSet.get(sendUserId) != null) { webSocketSet.get(sendUserId).sendMessage(userId + "給我發來消息,消息內容爲--->>" + message); } else { if (webSocketSet.get(userId) != null) { webSocketSet.get(userId).sendMessage("用戶id:" + sendUserId + "以離線,未收到您的信息!"); } System.out.println("消息接受人:" + sendUserId + "已經離線!"); } } catch (IOException e) { e.printStackTrace(); } } /** * 管理員發送消息 * * @param message */ public void systemSendToUser(String sendUserId, String message) { try { if (webSocketSet.get(sendUserId) != null) { webSocketSet.get(sendUserId).sendMessage("系統給我發來消息,消息內容爲--->>" + message); } else { System.out.println("消息接受人:" + sendUserId + "已經離線!"); } } catch (IOException e) { e.printStackTrace(); } } /** * 給全部人發消息 * * @param message */ private void sendAll(String message) { String sendMessage = message.split(",")[0]; //遍歷HashMap for (String key : webSocketSet.keySet()) { try { //判斷接收用戶是不是當前發消息的用戶 if (!userId.equals(key)) { webSocketSet.get(key).sendMessage("用戶:" + userId + "發來消息:" + " <br/> " + sendMessage); System.out.println("key = " + key); } } catch (IOException e) { e.printStackTrace(); } } } /** * 發生錯誤時調用 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { System.out.println("發生錯誤"); error.printStackTrace(); } /** * 發送消息 * * @param message * @throws IOException */ public void sendMessage(String message) throws IOException { //同步發送 this.session.getBasicRemote().sendText(message); //異步發送 //this.session.getAsyncRemote().sendText(message); } }
⑤、服務器推送接口:
IndexController: package cn.cnbuilder.websocket.controller; import cn.cnbuilder.websocket.ProductWebSocket; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.Map; import java.util.concurrent.TimeUnit; @Controller public class IndexController { @GetMapping(value = "/") @ResponseBody public Object index() { return "Hello,ALl。This is yuanmayouchuang webSocket demo!"; } @ResponseBody @GetMapping("test") public String test(String userId, String message) throws Exception { if (userId == "" || userId == null) { return "發送用戶id不能爲空"; } if (message == "" || message == null) { return "發送信息不能爲空"; } new ProductWebSocket().systemSendToUser(userId, message); return "發送成功!"; } @RequestMapping(value = "/ws") public String ws() { return "ws"; } @RequestMapping(value = "/ws1") public String ws1() { return "ws1"; } }
⑥:SpringBoot配置文件
application.yml: #端口號 server: port: 12006
⑦:動態banner:
banner.txt: __ ____ ____ _______ \ \ / / \/ \ \ / / ____| \ \_/ /| \ / |\ \_/ / | \ / | |\/| | \ /| | | | | | | | | | | |____ |_| |_| |_| |_| \_____|::猿碼優創:websocket SpringBoot Demo 博客地址:www.cnbuilder.cn
⑧:前段代碼:
ws.html: <!DOCTYPE html> <html> <head> <title>WebSocket SpringBootDemo</title> </head> <body> <!--userId:發送消息人的編號--> <div>默認用戶id:xiaoyou001(後期能夠根據業務邏輯替換)</div> <br/><input id="text" type="text"/> <input placeholder="請輸入接收人的用戶id" id="sendUserId"></input> <button onclick="send()">發送消息</button> <br/> <button onclick="closeWebSocket()">關閉WebSocket鏈接</button> <br/> <div>公衆號:猿碼優創</div> <br/> <div id="message"></div> </body> <script type="text/javascript"> var websocket = null; var userId = "xiaoyou001" //判斷當前瀏覽器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://127.0.0.1:12006/websocket/" + userId); } else { alert('當前瀏覽器不支持websocket哦!') } //鏈接發生錯誤的回調方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket鏈接發生錯誤"); }; //鏈接成功創建的回調方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket鏈接成功"); } //接收到消息的回調方法 websocket.onmessage = function (event) { setMessageInnerHTML(event.data); } //鏈接關閉的回調方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket鏈接關閉"); } //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。 window.onbeforeunload = function () { closeWebSocket(); } //將消息顯示在網頁上 function setMessageInnerHTML(sendMessage) { document.getElementById('message').innerHTML += sendMessage + '<br/>'; } //關閉WebSocket鏈接 function closeWebSocket() { websocket.close(); } //發送消息 function send() { var message = document.getElementById('text').value;//要發送的消息內容 if (message == "") { alert("發送信息不能爲空!") return; } //獲取發送人用戶id var sendUserId = document.getElementById('sendUserId').value; if (sendUserId == "") { alert("發送人用戶id不能爲空!") return; } document.getElementById('message').innerHTML += (userId + "給" + sendUserId + "發送消息,消息內容爲---->>" + message + '<br/>'); message = message + "," + sendUserId//將要發送的信息和內容拼起來,以便於服務端知道消息要發給誰 websocket.send(message); } </script> </html> ws1.html <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/html"> <head> <title>WebSocket SpringBootDemo</title> </head> <body> <!--userId:發送消息人的編號--> <div>默認用戶id:xiaoyou002(後期能夠根據業務邏輯替換)</div> <br/><input id="text" placeholder="請輸入要發送的信息" type="text"/> <input placeholder="請輸入接收人的用戶id" id="sendUserId"></input> <button onclick="send()">發送消息</button> <br/> <button onclick="closeWebSocket()">關閉WebSocket鏈接</button> <br/> <div>公衆號:猿碼優創</div> </br> <div id="message"></div> </body> <script type="text/javascript"> var websocket = null; var userId = "xiaoyou002" //判斷當前瀏覽器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://127.0.0.1:12006/websocket/" + userId); } else { alert('當前瀏覽器不支持websocket哦!') } //鏈接發生錯誤的回調方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket鏈接發生錯誤"); }; //鏈接成功創建的回調方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket鏈接成功"); } //接收到消息的回調方法 websocket.onmessage = function (event) { setMessageInnerHTML(event.data); } //鏈接關閉的回調方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket鏈接關閉"); } //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。 window.onbeforeunload = function () { closeWebSocket(); } //將消息顯示在網頁上 function setMessageInnerHTML(sendMessage) { document.getElementById('message').innerHTML += sendMessage + '<br/>'; } //關閉WebSocket鏈接 function closeWebSocket() { websocket.close(); } //發送消息 function send() { var message = document.getElementById('text').value;//要發送的消息內容 if (message == "") { alert("發送信息不能爲空!") return; } //獲取發送人用戶id var sendUserId = document.getElementById('sendUserId').value; if (sendUserId == "") { alert("發送人用戶id不能爲空!") return; } document.getElementById('message').innerHTML += ("我給" + sendUserId + "發送消息,消息內容爲---->>" + message + '<br/>'); message = message + "," + sendUserId//將要發送的信息和內容拼起來,以便於服務端知道消息要發給誰 websocket.send(message); } </script> </html>
測試:啓動項目
首頁訪問地址:http://127.0.0.1:12006/
訪問WebSocket測試頁面1:http://127.0.0.1:12006/ws
訪問流程圖:
測試一對一發送消息:給另外一個用戶發送信息
換一個瀏覽器,測試兩個 不要用同一瀏覽器,要不會出問題。
訪問WebSocket測試頁面2:http://127.0.0.1:12006/ws1
一對一發送消息牛成圖:
測試服務器主動向瀏覽器推送消息:http://127.0.0.1:12006/test?userId=xiaoyou002&message=我是小優,聽到請回答。
接口地址:http://127.0.0.1:12006/test
參數:userId:推送人用戶id ws.html :xiaoyou001 ws1.html:xiaoyou002 寫死的,可根據業務動態寫活。
一對多推送的話:流程和一對一類似。你們自行研究哈。
/** * 給全部人發消息 * * @param message */ private void sendAll(String message) { String sendMessage = message.split(",")[0]; //遍歷HashMap for (String key : webSocketSet.keySet()) { try { //判斷接收用戶是不是當前發消息的用戶 if (!userId.equals(key)) { webSocketSet.get(key).sendMessage("用戶:" + userId + "發來消息:" + " <br/> " + sendMessage); System.out.println("key = " + key); } } catch (IOException e) { e.printStackTrace(); } } }
終、、
以上就是websocket一對一發送消息,一對多發送消息,服務器主動推送消息 感受是否是超簡單! 有什麼問題能夠聯繫我哈。
鼓勵做者寫出更好的技術文檔,就請我喝一瓶哇哈哈哈哈哈哈哈。。
微信:
支付寶:
感謝一路支持個人人。。。。。
Love me and hold me
QQ:69673804(16年老號)
EMAIL:69673804@qq.com
友鏈交換
若是有興趣和本博客交換友鏈的話,請按照下面的格式在評論區進行評論,我會盡快添加上你的連接。
網站名稱:KingYiFan’S Blog
網站地址:http://blog.cnbuilder.cn
網站描述:年少是你未醒的夢話,風華是燃燼的彼岸花。
網站Logo/頭像:http://blog.cnbuilder.cn/upload/2018/7/avatar20180720144536200.jpg