Spring結合WebSocket實現(xiàn)實時通信的教程詳解
簡介
WebSocket 是基于TCP/IP協(xié)議,獨立于HTTP協(xié)議的通信協(xié)議。WebSocket 連接允許客戶端和服務器之間的全雙工通信,以便任何一方都可以通過已建立的連接將數(shù)據(jù)推送到另一方。
我們常用的HTTP是客戶端通過「請求-響應」的方式與服務器建立通信的,必須是客戶端主動觸發(fā)的行為,服務端只是做好接口被動等待請求。而在某些場景下的動作,是需要服務端主動觸發(fā)的,比如向客戶端發(fā)送消息、實時通訊、遠程控制等??蛻舳耸遣恢肋@些動作幾時觸發(fā)的,假如用HTTP的方式,那么設備端需要不斷輪詢服務端,這樣的方式對服務器壓力太大,同時產(chǎn)生很多無效請求,且具有延遲性。于是才采用可以建立雙向通訊的長連接協(xié)議。通過握手建立連接后,服務端可以實時發(fā)送數(shù)據(jù)與指令到設備端,服務器壓力小。
Spring WebSocket是Spring框架的一部分,提供了在Web應用程序中實現(xiàn)實時雙向通信的能力。本教程將引導你通過一個簡單的例子,演示如何使用Spring WebSocket建立一個實時通信應用。
準備工作
確保你的項目中已經(jīng)引入了Spring框架的WebSocket模塊。你可以通過Maven添加以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
創(chuàng)建WebSocket配置類(實現(xiàn)WebSocketConfigurer接口)
首先,創(chuàng)建一個配置類,用于配置WebSocket的相關設置。
package com.ci.erp.human.config;
import com.ci.erp.human.handler.WebSocketHandler;
import com.ci.erp.human.interceptor.WebSocketHandleInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
*
* Websocket配置類
*
* @author lucky_fd
* @since 2024-01-17
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注冊websocket處理器和攔截器
registry.addHandler(webSocketHandler(), "/websocket/server")
.addInterceptors(webSocketHandleInterceptor()).setAllowedOrigins("*");
registry.addHandler(webSocketHandler(), "/sockjs/server").setAllowedOrigins("*")
.addInterceptors(webSocketHandleInterceptor()).withSockJS();
}
@Bean
public WebSocketHandler webSocketHandler() {
return new WebSocketHandler();
}
@Bean
public WebSocketHandleInterceptor webSocketHandleInterceptor() {
return new WebSocketHandleInterceptor();
}
}
上面的配置類使用@EnableWebSocket注解啟用WebSocket,并通過registerWebSocketHandlers方法注冊WebSocket處理器。
- registerWebSocketHandlers:這個方法是向spring容器注冊一個handler處理器及對應映射地址,可以理解成MVC的Handler(控制器方法),websocket客戶端通過請求的url查找處理器進行處理
- addInterceptors:攔截器,當建立websocket連接的時候,我們可以通過繼承spring的HttpSessionHandshakeInterceptor來做一些事情。
- setAllowedOrigins:跨域設置,
*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的話默認localhost+本服務端口 - withSockJS: 這個是應對瀏覽器不支持websocket協(xié)議的時候降級為輪詢的處理。
創(chuàng)建WebSocket消息處理器(實現(xiàn)TextWebSocketHandler 接口)
接下來,創(chuàng)建一個消息處理器,處理客戶端發(fā)送的消息。
package com.ci.erp.human.handler;
import cn.hutool.core.util.ObjectUtil;
import com.ci.erp.common.core.utils.JsonUtils;
import com.ci.erp.human.domain.thirdVo.YYHeartbeat;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
*
* websocket處理類
* 實現(xiàn)WebSocketHandler接口
*
* - websocket建立連接后執(zhí)行afterConnectionEstablished回調接口
* - websocket關閉連接后執(zhí)行afterConnectionClosed回調接口
* - websocket接收客戶端消息執(zhí)行handleTextMessage接口
* - websocket傳輸異常時執(zhí)行handleTransportError接口
*
* @author lucky_fd
* @since 2024-01-17
*/
public class WebSocketHandler extends TextWebSocketHandler {
/**
* 存儲websocket客戶端連接
* */
private static final Map<String, WebSocketSession> connections = new HashMap<>();
/**
* 建立連接后觸發(fā)
* */
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("成功建立websocket連接");
// 建立連接后將連接以鍵值對方式存儲,便于后期向客戶端發(fā)送消息
// 以客戶端連接的唯一標識為key,可以通過客戶端發(fā)送唯一標識
connections.put(session.getRemoteAddress().getHostName(), session);
System.out.println("當前客戶端連接數(shù):" + connections.size());
}
/**
* 接收消息
* */
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到消息: " + message.getPayload());
// 收到客戶端請求消息后進行相應業(yè)務處理,返回結果
this.sendMessage(session.getRemoteAddress().getHostName(),new TextMessage("收到消息: " + message.getPayload()));
}
/**
* 傳輸異常處理
* */
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
}
/**
* 關閉連接時觸發(fā)
* */
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("觸發(fā)關閉websocket連接");
// 移除連接
connections.remove(session.getRemoteAddress().getHostName());
}
@Override
public boolean supportsPartialMessages() {
return super.supportsPartialMessages();
}
/**
* 向連接的客戶端發(fā)送消息
*
* @author lucky_fd
* @param clientId 客戶端標識
* @param message 消息體
**/
public void sendMessage(String clientId, TextMessage message) {
for (String client : connections.keySet()) {
if (client.equals(clientId)) {
try {
WebSocketSession session = connections.get(client);
// 判斷連接是否正常
if (session.isOpen()) {
session.sendMessage(message);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
break;
}
}
}
}
通過消息處理器,在開發(fā)中我們就可以實現(xiàn)向指定客戶端或所有客戶端發(fā)送消息,實現(xiàn)相應業(yè)務功能。
創(chuàng)建攔截器
攔截器會在握手時觸發(fā),可以用來進行權限驗證
package com.ci.erp.human.interceptor;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
/**
*
* Websocket攔截器類
*
* @author lucky_fd
* @since 2024-01-17
*/
public class WebSocketHandleInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println("攔截器前置觸發(fā)");
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
System.out.println("攔截器后置觸發(fā)");
super.afterHandshake(request, response, wsHandler, ex);
}
}
創(chuàng)建前端頁面客戶端
最后,創(chuàng)建一個簡單的HTML頁面,用于接收用戶輸入并顯示實時聊天信息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Spring WebSocket Chat</title>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
</head>
<body>
請輸入:<input type="text" id="message" placeholder="Type your message">
<button onclick="sendMessage()">Send</button>
<button onclick="websocketClose()">關閉連接</button>
<div id="chat"></div>
<script>
var socket = null;
if ('WebSocket' in window) {
// 后端服務port為22900
socket = new WebSocket("ws://localhost:22900/websocket/server");
} else if ('MozWebSocket' in window) {
socket = new MozWebSocket("ws://localhost:22900/websocket/server");
} else {
socket = new SockJS("http://localhost:22900/sockjs/server");
}
// 接收消息觸發(fā)
socket.onmessage = function (event) {
showMessage(event.data);
};
// 創(chuàng)建連接觸發(fā)
socket.onopen = function (event) {
console.log(event.type);
};
// 連接異常觸發(fā)
socket.onerror = function (event) {
console.log(event)
};
// 關閉連接觸發(fā)
socket.onclose = function (closeEvent) {
console.log(closeEvent.reason);
};
//發(fā)送消息
function sendMessage() {
if (socket.readyState === socket.OPEN) {
var message = document.getElementById('message').value;
socket.send(message);
console.log("發(fā)送成功!");
} else {
console.log("連接失敗!");
}
}
function showMessage(message) {
document.getElementById('chat').innerHTML += '<p>' + message + '</p>';
}
function websocketClose() {
socket.close();
console.log("連接關閉");
}
window.close = function () {
socket.onclose();
};
</script>
</body>
</html>
這個頁面使用了WebSocket對象來建立連接,并通過onmessage監(jiān)聽收到的消息。通過輸入框發(fā)送消息,將會在頁面上顯示。
測試結果:
后端日志:

前端界面:

Java客戶端
添加依賴
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.4.0</version>
</dependency>
創(chuàng)建客戶端類(繼承WebsocketClient)
package com.river.websocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
public class MyWebSocketClient extends WebSocketClient {
MyWebSocketClient(String url) throws URISyntaxException {
super(new URI(url));
}
// 建立連接
@Override
public void onOpen(ServerHandshake shake) {
System.out.println(shake.getHttpStatusMessage());
}
// 接收消息
@Override
public void onMessage(String paramString) {
System.out.println(paramString);
}
// 關閉連接
@Override
public void onClose(int paramInt, String paramString, boolean paramBoolean) {
System.out.println("關閉");
}
// 連接異常
@Override
public void onError(Exception e) {
System.out.println("發(fā)生錯誤");
}
}
測試websocket
package com.river.websocket;
import org.java_websocket.enums.ReadyState;
import java.net.URISyntaxException;
/**
* @author lucky_fd
* @date 2024-1-17
*/
public class Client {
public static void main(String[] args) throws URISyntaxException, InterruptedException {
MyWebSocketClient client = new MyWebSocketClient("ws://localhost:22900/websocket/server");
client.connect();
while (client.getReadyState() != ReadyState.OPEN) {
System.out.println("連接狀態(tài):" + client.getReadyState());
Thread.sleep(100);
}
client.send("測試數(shù)據(jù)!");
client.close();
}
}到此這篇關于Spring結合WebSocket實現(xiàn)實時通信的教程詳解的文章就介紹到這了,更多相關Spring WebSocket實時通信內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解mybatis插入數(shù)據(jù)后返回自增主鍵ID的問題
這篇文章主要介紹了mybatis插入數(shù)據(jù)后返回自增主鍵ID詳解,本文通過場景分析示例代碼相結合給大家介紹的非常詳細,需要的朋友可以參考下2021-07-07
Java使用CompletableFuture進行非阻塞IO詳解
這篇文章主要介紹了Java使用CompletableFuture進行非阻塞IO詳解,CompletableFuture是Java中的一個類,用于支持異步編程和處理異步任務的結果,它提供了一種方便的方式來處理異步操作,并允許我們以非阻塞的方式執(zhí)行任務,需要的朋友可以參考下2023-09-09
使用Java自定義注解實現(xiàn)一個簡單的令牌桶限流器
限流是在分布式系統(tǒng)中常用的一種策略,它可以有效地控制系統(tǒng)的訪問流量,保證系統(tǒng)的穩(wěn)定性和可靠性,在本文中,我將介紹如何使用Java自定義注解來實現(xiàn)一個簡單的令牌桶限流器,需要的朋友可以參考下2023-10-10
將ResultSet中得到的一行或多行結果集封裝成對象的實例
這篇文章主要介紹了將ResultSet中得到的一行或多行結果集封裝成對象的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05

