您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何用單線程和定時(shí)任務(wù)分別實(shí)現(xiàn)WebSocket聊天室”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何用單線程和定時(shí)任務(wù)分別實(shí)現(xiàn)WebSocket聊天室”吧!
多媒體實(shí)時(shí)聊天
股票基金等數(shù)據(jù)報(bào)價(jià)
位置定位
社交訂閱
數(shù)據(jù)庫更新,前端實(shí)時(shí)顯示
要實(shí)現(xiàn)這種實(shí)時(shí)性較強(qiáng)的功能,以前經(jīng)常采用的方法:輪詢和Comet技術(shù)
總結(jié):都是基于請(qǐng)求-應(yīng)答模式,不算真正意義上的實(shí)時(shí)技術(shù),每一次請(qǐng)求應(yīng)答,都要消耗一定流量。
WebSocket協(xié)議基于TCP協(xié)議實(shí)現(xiàn),工作流程是這 樣的:瀏覽器通過JavaScript向服務(wù)端發(fā)出建立WebSocket連接的請(qǐng)求,在WebSocket連接建立成功后,客戶端和服務(wù)端就可以通過 TCP連接傳輸數(shù)據(jù)。因?yàn)閃ebSocket連接本質(zhì)上是TCP連接,不需要每次傳輸都帶上重復(fù)的頭部數(shù)據(jù), 其優(yōu)點(diǎn):
通過第一次Http Request第一次建立連接之后,之后的數(shù)據(jù)交換都不要再重新發(fā)送Http Request,節(jié)省了寬帶資源
WebSocket協(xié)議是雙向通信協(xié)議,既可以發(fā)送又可以接受
多路復(fù)用即多個(gè)不同的URL可以復(fù)用同一個(gè)Websocket連接
溫馨提示:基于IAEA+SpringBoot+Gradle開發(fā),得益于SpringBoot提供的自動(dòng)配置,只需要通過簡單注解@ServerEndpoint就能創(chuàng)建WebSocket服務(wù)端,再通過簡單的回調(diào)函數(shù)就能完成WebSocket服務(wù)端的編寫!
build.gadle
//spring-boot-starter-websocket的依賴springboot的高級(jí)組件會(huì)自動(dòng)引用基礎(chǔ)的組件, // 像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '2.1.6.RELEASE' //thymeleaf compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.1.6.RELEASE'
創(chuàng)建一個(gè)WebSocketConfig
package com.example.SmartHome.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /* *@Description: 自動(dòng)注冊(cè)使用了@ServerEndpoint注解聲明的Websocket endpoint *@ClassName: WebSocketConfig *@Author: zzq *@Date: 2019/7/7 11:01 *@Version: 1.0 */ @Configuration @Component @ConditionalOnWebApplication public class WebSocketConfig { /** * 自動(dòng)注冊(cè)使用了@ServerEndpoint注解聲明的Websocket endpoint */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
提醒:ServerEndpointExporter 是由Spring官方提供的標(biāo)準(zhǔn)實(shí)現(xiàn),用于掃描ServerEndpointConfig配置類和@ServerEndpoint注解實(shí)例。使用規(guī)則:1.如果使用默認(rèn)的嵌入式容器 比如Tomcat 則必須手工在上下文提供ServerEndpointExporter。2. 如果使用外部容器部署war包,則不要提供提供ServerEndpointExporter,因?yàn)榇藭r(shí)SpringBoot默認(rèn)將掃描服務(wù)端的行為交給外部容器處理。
創(chuàng)建WebSocket服務(wù)器
核心思路: ① 通過注解@ServerEndpoint來聲明實(shí)例化WebSocket服務(wù)端。 ② 通過注解@OnOpen、@OnMessage、@OnClose、@OnError 來聲明回調(diào)函數(shù)。
事件類型 | 注解 | 事件描述 |
open | @OnOpen | 當(dāng)打開連接后觸發(fā) |
message | @OnMessage | 當(dāng)接受客戶端消息時(shí)觸發(fā) |
error | @OnError | 當(dāng)通信異常時(shí)觸發(fā) |
close | @OnClose | 當(dāng)連接關(guān)閉時(shí)觸發(fā) |
package com.example.SmartHome.server; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; /* *@Description: WebSocketServer服務(wù)器端 *@ClassName: WebSocketServer *@Author: zzq *@Date: 2019/7/3 17:05 *@Version: 1.0 */ //ServerEndpoint這個(gè)bean會(huì)自動(dòng)注冊(cè)使用了@ServerEndpoint注解聲明的Websocket endpoint。 // 要注意,如果使用獨(dú)立的servlet容器,而不是直接使用springboot的內(nèi)置容器, // 就不要注入ServerEndpointExporter, // 因?yàn)樗鼘⒂扇萜髯约禾峁┖凸芾怼? /** * @ServerEndpoint 注解是一個(gè)類層次的注解,它的功能主要是將目前的類定義成一個(gè)websocket服務(wù)器端, * 注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址,客戶端可以通過這個(gè)URL來連接到WebSocket服務(wù)器端 */ @ServerEndpoint("/websocket") @Component //它的主要作用就是將這個(gè)監(jiān)聽器納入到Spring容器中進(jìn)行管理 public class WebSocket { // MyThread thread1=new MyThread(); // Thread thread =new Thread(thread1); //每個(gè)客戶端都會(huì)有相應(yīng)的session,服務(wù)端可以發(fā)送相關(guān)消息 private Session session; public static int onlineCount = 0; //J.U.C包下線程安全的類,主要用來存放每個(gè)客戶端對(duì)應(yīng)的webSocket連接 private static CopyOnWriteArraySet<WebSocket> copyOnWriteArraySet = new CopyOnWriteArraySet<WebSocket>(); /** * @Name:onOpen * @Description:打開連接。進(jìn)入頁面后會(huì)自動(dòng)發(fā)請(qǐng)求到此進(jìn)行連接 */ @OnOpen public void onOpen(Session session) throws IOException { this.session = session; copyOnWriteArraySet.add(this); addOnlineCount(); System.out.println("websocket有新的連接, 總數(shù):" + getOnlineCount()); sendMessage("成功連接"); } /** * @Name:onClose * @Description:用戶關(guān)閉頁面,即關(guān)閉連接 */ @OnClose public void onClose() { copyOnWriteArraySet.remove(this); shortOnlineCount(); System.out.println("websocket連接斷開, 總數(shù):" + getOnlineCount()); } /** * @Name:onMessage * @Description:收到客戶端消息后調(diào)用的方法 */ @OnMessage public void onMessage(String message,Session session) throws IOException { System.out.println("websocket收到客戶端發(fā)來的消息:" + message); for(WebSocket webSocket:copyOnWriteArraySet){ webSocket.sendMessage(message); } } /** * @Name:onError * @Description:出現(xiàn)錯(cuò)誤 */ @OnError public void onError(Session session, Throwable error) { System.out.println("發(fā)生錯(cuò)誤:" + error.getMessage() + "; sessionId:" + session.getId()); error.printStackTrace(); } public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); // this.session.getAsyncRemote().sendText(message); } public void sendMessage(Object object) { //遍歷客戶端 for (WebSocket webSocket : copyOnWriteArraySet) { System.out.println("websocket廣播消息:" + object.toString()); try { //服務(wù)器主動(dòng)推送 webSocket.session.getBasicRemote().sendObject(object); } catch (Exception e) { e.printStackTrace(); } } } /** * @Name:sendMessage * @Description:用于發(fā)送給客戶端消息(群發(fā)) */ public static void sendInfo(String message) { //遍歷客戶端 for (WebSocket webSocket : copyOnWriteArraySet) { System.out.println("websocket廣播消息:" + message); try { //服務(wù)器主動(dòng)推送 webSocket.session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } /** * @Name:sendMessage * @Description:用于發(fā)送給指定客戶端消息 */ public void sendMessage(String sessionId, String message) throws IOException { Session session = null; WebSocket tempWebSocket = null; for (WebSocket webSocket : copyOnWriteArraySet) { if (webSocket.session.getId().equals(sessionId)) { tempWebSocket = webSocket; session = webSocket.session; break; } } if (session != null) { tempWebSocket.session.getBasicRemote().sendText(message); } else { System.out.println("沒有找到你指定ID的會(huì)話:{}" + "; sessionId:" + sessionId); } } public static synchronized int getOnlineCount(){ return onlineCount; } public static synchronized void addOnlineCount(){ WebSocket.onlineCount++; } public static synchronized void shortOnlineCount(){ WebSocket.onlineCount--; } }
package com.example.SmartHome.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /* *@Description: TODO *@ClassName: ChatController *@Author: zzq *@Date: 2019/7/9 16:56 *@Version: 1.0 */ @Controller public class ChatController { @RequestMapping("/websocket") public String init() { return "websocket.html"; } }
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>My WebSocket Test</title> </head> <body> Welcome<br/> <input id="text" type="text" /> <button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button> <div id="message"> </div> </body> <script type="text/javascript"> var websocket = null; //判斷當(dāng)前瀏覽器是否支持WebSocket if('WebSocket' in window){ websocket = new WebSocket("ws:2559qs1996.qicp.vip:20422/websocket"); } else{ alert('Not support websocket') } //連接發(fā)生錯(cuò)誤的回調(diào)方法 websocket.onerror = function(){ setMessageInnerHTML("error"); }; //連接成功建立的回調(diào)方法 websocket.onopen = function(event){ setMessageInnerHTML("open"); } //接收到消息的回調(diào)方法 websocket.onmessage = function(event){ setMessageInnerHTML(event.data); } //連接關(guān)閉的回調(diào)方法 websocket.onclose = function(){ setMessageInnerHTML("close"); } //監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會(huì)拋異常。 window.onbeforeunload = function(){ websocket.close(); } //將消息顯示在網(wǎng)頁上 function setMessageInnerHTML(innerHTML){ document.getElementById('message').innerHTML += innerHTML + '<br/>'; } //關(guān)閉連接 function closeWebSocket(){ websocket.close(); } //發(fā)送消息 function send(){ var message = document.getElementById('text').value; websocket.send(message); } </script> </html>
6.結(jié)果展示
服務(wù)端:
感謝各位的閱讀,以上就是“如何用單線程和定時(shí)任務(wù)分別實(shí)現(xiàn)WebSocket聊天室”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)如何用單線程和定時(shí)任務(wù)分別實(shí)現(xiàn)WebSocket聊天室這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。