SpringBoot+WebSocket實(shí)現(xiàn)即時通訊功能(Spring方式)
什么是websocket?
WebSocket是一種在單個TCP連接上進(jìn)行全雙工通信的協(xié)議。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并由RFC7936補(bǔ)充規(guī)范。WebSocket API也被W3C定為標(biāo)準(zhǔn)。
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù)。在WebSocket API中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。
為什么有了HTTP協(xié)議還要WebSocket
HTTP協(xié)議采用的是客戶端(瀏覽器)輪詢的方式,即客戶端發(fā)送請求,服務(wù)端做出響應(yīng),為了獲取最新的數(shù)據(jù),需要不斷的輪詢發(fā)出HTTP請求,占用大量帶寬。
WebSocket采用了一些特殊的報(bào)頭,使得瀏覽器和服務(wù)器只需要通過“握手”建立一條連接通道后,此鏈接保持活躍狀態(tài),之后的客戶端和服務(wù)器的通信都使用這個連接,解決了Web實(shí)時性的問題,相比于HTTP有一下好處:
一個Web客戶端只建立一個TCP連接
WebSocket服務(wù)端可以主動推送(push)數(shù)據(jù)到Web客戶端
有更加輕量級的頭,減少了數(shù)據(jù)傳輸量
特點(diǎn)
- 建立在TCP協(xié)議只上,服務(wù)端比較容易實(shí)現(xiàn)
- 于HTTP協(xié)議有良好的兼容性,默認(rèn)端口也是80和443,握手階段使用HTTP協(xié)議,因此握手時不容易屏蔽,能通過各種HTTP代理服務(wù)器
- 數(shù)據(jù)格式輕量,通信高效且節(jié)省帶寬
- 支持傳輸文本數(shù)據(jù)和二進(jìn)制數(shù)據(jù)
- 沒有同源限制,客戶端可以與任意服務(wù)器通信
- 也支持加密傳輸,WS+SSL,URL形如wss://
技術(shù)
- jdk8
- maven
- SpringBoot2.7.4
- thmeleaf
- websocket
實(shí)現(xiàn)(簡易websocket聊天室)
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 https://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.7.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>websocket</artifactId> <version>0.0.1-SNAPSHOT</version> <name>websocket</name> <description>websocket</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
websocketHandler.java
package com.example.websocket.handler; import org.springframework.stereotype.Component; import org.springframework.web.socket.*; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @Component public class WebsocketHandler implements WebSocketHandler { private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { System.out.println("連接成功"+session.getId()); SESSIONS.put(session.getId(), session); System.out.println("當(dāng)前在線人數(shù):"+SESSIONS.size()); } @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { System.out.println("接收消息"+session.getId()); String msg = message.getPayload().toString(); System.out.println(msg); // session.sendMessage(message); sendMessageToAllUsers(session.getId()+":"+msg); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { System.out.println("連接出錯"+session.getId()); if (!session.isOpen()) { SESSIONS.remove(session.getId()); session.close(); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { System.out.println("關(guān)閉連接"+session.getId()); if(!session.isOpen()){ SESSIONS.remove(session.getId()); System.out.println("當(dāng)前在線人數(shù):"+SESSIONS.size()); } } @Override public boolean supportsPartialMessages() { return false; } /** * sendMessageToUser:發(fā)給指定用戶 * */ public void sendMessageToUser(String userId, String contents) { WebSocketSession session = SESSIONS.get(userId); if (session != null && session.isOpen()) { try { TextMessage message = new TextMessage(contents); session.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * sendMessageToAllUsers:發(fā)給所有的用戶 * */ public void sendMessageToAllUsers(String contents) { Set<String> userIds = SESSIONS.keySet(); for (String userId : userIds) { this.sendMessageToUser(userId, contents); } } }
WebsocketInterceptor.java
package com.example.websocket.interceptor; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import java.util.Map; @Component public class WebsocketInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { System.out.println("前置攔截"); return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { System.out.println("后置攔截"); } }
WebsocketConfig.java
package com.example.websocket.config; import com.example.websocket.handler.WebsocketHandler; import com.example.websocket.interceptor.WebsocketInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Component @EnableWebSocket public class WebsocketConfig implements WebSocketConfigurer { @Autowired private WebsocketHandler websocketHandler; @Autowired private WebsocketInterceptor websocketInterceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(websocketHandler, "/ws").setAllowedOrigins("*").addInterceptors(websocketInterceptor); } }
WebsocketController.java
package com.example.websocket.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class WebsocketController { @GetMapping("/") public String init() { return "websocket"; } @GetMapping("/chat") public String chat() { return "chatRoom"; } }
WebsocketApplication.java
package com.example.websocket; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class WebsocketApplication { public static void main(String[] args) { SpringApplication.run(WebsocketApplication.class, args); } }
前端
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>My WebSocket Test</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" rel="external nofollow" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"> <!-- 可選的 Bootstrap 主題文件(一般不用引入) --> <link rel="stylesheet" rel="external nofollow" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous"> <!-- 最新的 Bootstrap 核心 JavaScript 文件 --> <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script> </head> <body> Welcome<br/> <div id="message"></div> <input id="text" type="text" /> <button class="btn btn-primary" onclick="send()">Send</button> <button class="btn btn-danger"onclick="closeWebSocket()">Close</button> </body> <script type="text/javascript"> var websocket = null; //判斷當(dāng)前瀏覽器是否支持WebSocket if('WebSocket' in window){ websocket = new WebSocket("ws://localhost:8080/ws"); } else{ alert('Not support websocket') } //連接發(fā)生錯誤的回調(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)閉時,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會拋異常。 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); message.value(""); } </script> </html>
測試
先啟動服務(wù)
然后我們就可以愉快聊天了
結(jié)尾
到這我們的簡單版的ws長連接聊天室就做好了,以上就是SpringBoot+WebSocket實(shí)現(xiàn)即時通訊功能(Spring方式)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot+WebSocket即時通訊的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中print、printf、println的區(qū)別
這篇文章主要介紹了Java中print、printf、println的區(qū)別的相關(guān)資料,需要的朋友可以參考下2023-03-03Java中Executor和Executors的區(qū)別小結(jié)
在Java并發(fā)編程中,Executor是一個核心接口,提供了任務(wù)執(zhí)行的抽象方法,而Executors是一個工具類,提供了創(chuàng)建各種線程池的工廠方法,Executor關(guān)注任務(wù)的執(zhí)行,而Executors關(guān)注如何創(chuàng)建適合的執(zhí)行器,感興趣的可以了解一下2024-10-10java?http請求設(shè)置代理Proxy的兩種常見方法
代理是一種常見的設(shè)計(jì)模式,其目的就是為其他對象提供一個代理以控制對某個對象的訪問,這篇文章主要給大家介紹了關(guān)于java?http請求設(shè)置代理Proxy的兩種常見方法,需要的朋友可以參考下2023-11-11基于SpringBoot實(shí)現(xiàn)大文件分塊上傳功能
這篇文章主要介紹了基于SpringBoot實(shí)現(xiàn)大文件分塊上傳功能,實(shí)現(xiàn)原理其實(shí)很簡單,核心就是客戶端把大文件按照一定規(guī)則進(jìn)行拆分,比如20MB為一個小塊,分解成一個一個的文件塊,然后把這些文件塊單獨(dú)上傳到服務(wù)端,需要的朋友可以參考下2024-09-09