SpringBoot整合WebSocket實(shí)現(xiàn)實(shí)時(shí)通信功能
什么是WebSocket?
WebSocket是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議。與傳統(tǒng)的HTTP請(qǐng)求-響應(yīng)模式不同,WebSocket允許服務(wù)器主動(dòng)向客戶端推送數(shù)據(jù),實(shí)現(xiàn)了實(shí)時(shí)通信的功能。WebSocket協(xié)議基于HTTP協(xié)議,通過在握手階段升級(jí)協(xié)議,使得服務(wù)器和客戶端可以直接進(jìn)行數(shù)據(jù)交換,而無需頻繁的HTTP請(qǐng)求。
Spring Boot中的WebSocket支持
Spring Boot提供了對(duì)WebSocket的支持,通過集成Spring WebSocket模塊,我們可以輕松地實(shí)現(xiàn)WebSocket功能。在Spring Boot中,我們可以使用注解來定義WebSocket的處理器和消息處理方法,從而實(shí)現(xiàn)實(shí)時(shí)通信。
WebSocket和HTTP優(yōu)劣勢(shì)

WebSocket的優(yōu)勢(shì):
1.實(shí)時(shí)性:
WebSocket是一種全雙工通信協(xié)議,可以實(shí)現(xiàn)服務(wù)器主動(dòng)向客戶端推送數(shù)據(jù),實(shí)現(xiàn)實(shí)時(shí)通信。相比之下,HTTP是一種請(qǐng)求-響應(yīng)模式 的協(xié)議,需要客戶端主動(dòng)發(fā)起請(qǐng)求才能獲取數(shù)據(jù)。
2.較低的延遲:
由于WebSocket使用單個(gè)TCP連接進(jìn)行通信,避免了HTTP的握手和頭部信息的重復(fù)傳輸,因此具有較低的延遲。
3.較小的數(shù)據(jù)傳輸量:
WebSocket使用二進(jìn)制數(shù)據(jù)幀進(jìn)行傳輸,相比于HTTP的文本數(shù)據(jù)傳輸,可以減少數(shù)據(jù)傳輸量,提高傳輸效率。
4.更好的兼容性:
WebSocket協(xié)議可以在多種瀏覽器和平臺(tái)上使用,具有較好的兼容性。
HTTP的優(yōu)勢(shì):
1.簡(jiǎn)單易用:
? HTTP是一種簡(jiǎn)單的請(qǐng)求-響應(yīng)協(xié)議,易于理解和使用。相比之下,WebSocket需要進(jìn)行握手和協(xié)議升級(jí)等復(fù)雜操作。
2.更廣泛的應(yīng)用:
HTTP協(xié)議廣泛應(yīng)用于Web開發(fā)中,支持各種類型的請(qǐng)求和響應(yīng),可以用于傳輸文本、圖片、視頻等多種數(shù)據(jù)格式。
3.更好的安全性:
HTTP協(xié)議支持HTTPS加密傳輸,可以保證數(shù)據(jù)的安全性。
綜上,WebSocket適用于需要實(shí)時(shí)通信和較低延遲的場(chǎng)景,而HTTP適用于傳輸各種類型的數(shù)據(jù)和簡(jiǎn)單的請(qǐng)求-響應(yīng)模式。在實(shí)際應(yīng)用中,可以根據(jù)具體需求選擇合適的協(xié)議。
示例
版本依賴
| 模塊 | 版本 |
|---|---|
| SpringBoot | 3.1.0 |
| JDK | 17 |
代碼
WebSocketConfig
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocketServer
@Component
@ServerEndpoint("/server/{uid}")
@Slf4j
public class WebSocketServer {
/**
* 記錄當(dāng)前在線連接數(shù)
*/
private static int onlineCount = 0;
/**
* 使用線程安全的ConcurrentHashMap來存放每個(gè)客戶端對(duì)應(yīng)的WebSocket對(duì)象
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 與某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
*/
private Session session;
/**
* 接收客戶端消息的uid
*/
private String uid = "";
/**
* 連接建立成功調(diào)用的方法
* @param session
* @param uid
*/
@OnOpen
public void onOpen(Session session, @PathParam("uid") String uid) {
this.session = session;
this.uid = uid;
if (webSocketMap.containsKey(uid)) {
webSocketMap.remove(uid);
//加入到set中
webSocketMap.put(uid, this);
} else {
//加入set中
webSocketMap.put(uid, this);
//在線數(shù)加1
addOnlineCount();
}
log.info("用戶【" + uid + "】連接成功,當(dāng)前在線人數(shù)為:" + getOnlineCount());
try {
sendMsg("連接成功");
} catch (IOException e) {
log.error("用戶【" + uid + "】網(wǎng)絡(luò)異常!", e);
}
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(uid)) {
webSocketMap.remove(uid);
//從set中刪除
subOnlineCount();
}
log.info("用戶【" + uid + "】退出,當(dāng)前在線人數(shù)為:" + getOnlineCount());
}
/**
* 收到客戶端消息后調(diào)用的方法
* @param message 客戶端發(fā)送過來的消息
* @param session 會(huì)話
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用戶【" + uid + "】發(fā)送報(bào)文:" + message);
//群發(fā)消息
//消息保存到數(shù)據(jù)庫或者redis
if (StringUtils.isNotBlank(message)) {
try {
//解析發(fā)送的報(bào)文
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> map = objectMapper.readValue(message, new TypeReference<Map<String, String>>(){});
//追加發(fā)送人(防止串改)
map.put("fromUID", this.uid);
String toUID = map.get("toUID");
//傳送給對(duì)應(yīng)的toUserId用戶的WebSocket
if (StringUtils.isNotBlank(toUID) && webSocketMap.containsKey(toUID)) {
webSocketMap.get(toUID).sendMsg(objectMapper.writeValueAsString(map));
} else {
//若果不在這個(gè)服務(wù)器上,可以考慮發(fā)送到mysql或者redis
log.error("請(qǐng)求目標(biāo)用戶【" + toUID + "】不在該服務(wù)器上");
}
} catch (Exception e) {
log.error("用戶【" + uid + "】發(fā)送消息異常!", e);
}
}
}
/**
* 處理錯(cuò)誤
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用戶【" + this.uid + "】處理消息錯(cuò)誤,原因:" + error.getMessage());
error.printStackTrace();
}
/**
* 實(shí)現(xiàn)服務(wù)器主動(dòng)推送
* @param msg
* @throws IOException
*/
private void sendMsg(String msg) throws IOException {
this.session.getBasicRemote().sendText(msg);
}
/**
* 發(fā)送自定義消息
* @param message
* @param uid
* @throws IOException
*/
public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException {
log.info("發(fā)送消息到用戶【" + uid + "】發(fā)送的報(bào)文:" + message);
if (!StringUtils.isEmpty(uid) && webSocketMap.containsKey(uid)) {
webSocketMap.get(uid).sendMsg(message);
} else {
log.error("用戶【" + uid + "】不在線!");
}
}
private static synchronized int getOnlineCount() {
return onlineCount;
}
private static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
private static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
WebSocketController
@RestController
public class WebSocketController {
@GetMapping("/page")
public ModelAndView page() {
return new ModelAndView("webSocket");
}
@RequestMapping("/push/{toUID}")
public ResponseEntity<String> pushToClient(String message, @PathVariable String toUID) throws Exception {
WebSocketServer.sendInfo(message, toUID);
return ResponseEntity.ok("Send Success!");
}
}
webSocket.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket消息通知</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var socket;
//打開WebSocket
function openSocket() {
if (typeof (WebSocket) === "undefined") {
console.log("您的瀏覽器不支持WebSocket");
} else {
console.log("您的瀏覽器支持WebSocket");
//實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口,建立連接.
var socketUrl = "http://localhost:8080/socket/server/" + $("#uid").val();
//將https與http協(xié)議替換為ws協(xié)議
socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
console.log(socketUrl);
if (socket != null) {
socket.close();
socket = null;
}
socket = new WebSocket(socketUrl);
//打開事件
socket.onopen = function () {
console.log("WebSocket已打開");
//socket.send("這是來自客戶端的消息" + location.href + new Date());
};
//獲得消息事件
socket.onmessage = function (msg) {
console.log(msg.data);
//發(fā)現(xiàn)消息進(jìn)入,開始處理前端觸發(fā)邏輯
};
//關(guān)閉事件
socket.onclose = function () {
console.log("WebSocket已關(guān)閉");
};
//發(fā)生了錯(cuò)誤事件
socket.onerror = function () {
console.log("WebSocket發(fā)生了錯(cuò)誤");
}
}
}
//發(fā)送消息
function sendMessage() {
if (typeof (WebSocket) === "undefined") {
console.log("您的瀏覽器不支持WebSocket");
} else {
console.log("您的瀏覽器支持WebSocket");
console.log('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');
socket.send('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');
}
}
</script>
<body>
<p>【uid】:
<div><input id="uid" name="uid" type="text" value="1"></div>
<p>【toUID】:
<div><input id="toUID" name="toUID" type="text" value="2"></div>
<p>【Msg】:
<div><input id="msg" name="msg" type="text" value="hello WebSocket2"></div>
<p>【第一步操作:】:
<div>
<button onclick="openSocket()">開啟socket</button>
</div>
<p>【第二步操作:】:
<div>
<button onclick="sendMessage()">發(fā)送消息</button>
</div>
</body>
</html>
測(cè)試
打開2個(gè)頁面
第一個(gè):
http://localhost:8080/socket/page

第二個(gè):
http://localhost:8080/socket/page

都點(diǎn)擊開啟socket

都點(diǎn)擊發(fā)送

至此示例發(fā)送完成
總結(jié)
通過本文的介紹,我們了解了Spring Boot中如何集成WebSocket,實(shí)現(xiàn)實(shí)時(shí)通信的功能。
WebSocket作為一種高效的實(shí)時(shí)通信協(xié)議,為開發(fā)者提供了更好的用戶體驗(yàn)和交互性。
希望本文能夠幫助快速掌握Spring Boot整合WebSocket的方法,為應(yīng)用程序添加實(shí)時(shí)通信功能。
以上就是SpringBoot整合WebSocket實(shí)現(xiàn)實(shí)時(shí)通信功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot WebSocket通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
netty服務(wù)端處理請(qǐng)求聯(lián)合pipeline分析
這篇文章主要為大家介紹了netty服務(wù)端處理請(qǐng)求聯(lián)合pipeline示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Java 常見異常(Runtime Exception )詳細(xì)介紹并總結(jié)
這篇文章主要介紹了Java 常見異常(Runtime Exception )詳細(xì)介紹并相關(guān)資料,大家在開發(fā)Java 應(yīng)用軟件的時(shí)候經(jīng)常會(huì)遇到各種異常這里幫大家整理了一部分,并解釋如何解決,需要的朋友可以參考下2016-10-10
Java實(shí)現(xiàn)Executors類創(chuàng)建常見線程池
本文主要介紹了Java實(shí)現(xiàn)Executors類創(chuàng)建常見線程池,在Java中,可以通過Executors工廠類提供四種常見類型的線程池,下面就來介紹一下這四種的方法實(shí)現(xiàn),感興趣的可以了解一下2023-11-11
Java通過JNI調(diào)用C++動(dòng)態(tài)庫的完整流程詳解
JNI(Java Native Interface),是實(shí)現(xiàn)Java/Kotlin與C/C++語言之間交互的橋梁,本文主要為大家介紹了Java通過JNI調(diào)用C++動(dòng)態(tài)庫的完整流程,需要的可以參考下2025-04-04
SpringBoot 將多個(gè)Excel打包下載的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot 將多個(gè)Excel打包下載的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
java 定義長(zhǎng)度為0的數(shù)組/空數(shù)組案例
這篇文章主要介紹了java 定義長(zhǎng)度為0的數(shù)組/空數(shù)組案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-03-03
關(guān)于HashMap 并發(fā)時(shí)會(huì)引起死循環(huán)的問題解析
JDK1.8之前采用頭插,即在鏈表結(jié)構(gòu)上每次都把數(shù)據(jù)放在鏈表頭部。JDK1.8采用尾插方法,很多朋友在學(xué)習(xí)Java并發(fā)容器和框架時(shí),看到為什么要使用ConcurrentHashMap時(shí)不知道究其原因,今天小編通過本文給大家介紹下HashMap 并發(fā)死循環(huán)問題,一起看看吧2021-05-05
Mybatis多表關(guān)聯(lián)查詢的實(shí)現(xiàn)(DEMO)
本節(jié)要實(shí)現(xiàn)的是多表關(guān)聯(lián)查詢的簡(jiǎn)單demo。場(chǎng)景是根據(jù)id查詢某商品分類信息,并展示該分類下的商品列表,需要的朋友可以參考下2017-02-02

