? ? ? 假設(shè)有這樣一個場景:服務(wù)端的資源經(jīng)常在更新,客戶端需要盡量及時地了解到這些更新發(fā)生后展示給用戶,如果是 HTTP 1.1,通常會開啟 ajax 請求詢問服務(wù)端是否有更新,通過定時器反復(fù)輪詢服務(wù)端響應(yīng)的資源是否有更新。
? ? ? ? ? ?
? ??
? ? ? ?在長時間不更新的情況下,反復(fù)地去詢問會對服務(wù)器造成很大的壓力,對網(wǎng)絡(luò)也有很大的消耗,如果定時的時間比較大,服務(wù)端有更新的話,客戶端可能需要等待定時器達(dá)到以后才能獲知,這個信息也不能很及時地獲取到。
? ? ? 而有了 WebSocket 協(xié)議,就能很好地解決這些問題,WebSocket 可以反向通知的,通常向服務(wù)端訂閱一類消息,服務(wù)端發(fā)現(xiàn)這類消息有更新就會不停地通知客戶端。
WebSocket 簡介
? ? ?WebSocket 協(xié)議是基于 TCP 的一種新的網(wǎng)絡(luò)協(xié)議,它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信—允許服務(wù)器主動發(fā)送信息給客戶端,這樣就可以實(shí)現(xiàn)從客戶端發(fā)送消息到服務(wù)器,而服務(wù)器又可以轉(zhuǎn)發(fā)消息到客戶端,這樣就能夠?qū)崿F(xiàn)客戶端之間的交互。對于 WebSocket 的開發(fā),Spring 也提供了良好的支持,目前很多瀏覽器已經(jīng)實(shí)現(xiàn)了 WebSocket 協(xié)議,但是依舊存在著很多瀏覽器沒有實(shí)現(xiàn)該協(xié)議,為了兼容那些沒有實(shí)現(xiàn)該協(xié)議的瀏覽器,往往還需要通過 STOMP 協(xié)議來完成這些兼容。
下面我們在 Spring Boot 中集成 WebSocket 來實(shí)現(xiàn)服務(wù)端推送消息到客戶端。
Spring Boot 集成 WebSocket
首先創(chuàng)建一個 Spring Boot 項(xiàng)目,然后在?pom.xml
?加入如下依賴集成 WebSocket:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
開啟配置
? ? 接下來在?config
?包下創(chuàng)建一個 WebSocket 配置類?WebSocketConfiguration
,在配置類上加入注解?@EnableWebSocket
,表明開啟 WebSocket,內(nèi)部實(shí)例化?ServerEndpointExporter
?的 Bean,該 Bean 會自動注冊?@ServerEndpoint
?注解聲明的端點(diǎn),代碼如下:?
@Configuration
@EnableWebSocket
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
?編寫端點(diǎn)服務(wù)類
? ? 接下來使用?@ServerEndpoint
?定義一個端點(diǎn)服務(wù)類,在端點(diǎn)服務(wù)類中,可以定義 WebSocket 的打開、關(guān)閉、錯誤和發(fā)送消息的方法,具體代碼如下所示:
package worn.xiao.riskmsg.service;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 當(dāng)前在線連接數(shù)
*/
private static AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 用來存放每個客戶端對應(yīng)的 WebSocketServer 對象
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
*/
private Session session;
/**
* 接收 userId
*/
private String userId = "";
/**
* 連接建立成功調(diào)用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
} else {
webSocketMap.put(userId, this);
addOnlineCount();
}
log.info("用戶連接:" + userId + ",當(dāng)前在線人數(shù)為:" + getOnlineCount());
try {
sendMessage("連接成功!");
} catch (IOException e) {
log.error("用戶:" + userId + ",網(wǎng)絡(luò)異常!!!!!!");
}
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
subOnlineCount();
}
log.info("用戶退出:" + userId + ",當(dāng)前在線人數(shù)為:" + getOnlineCount());
}
/**
* 收到客戶端消息后調(diào)用的方法
*
* @param message 客戶端發(fā)送過來的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用戶消息:" + userId + ",報文:" + message);
if (!StringUtils.isEmpty(message)) {
try {
JSONObject jsonObject = JSONObject.parseObject(message);
jsonObject.put("fromUserId", this.userId);
String toUserId = jsonObject.getString("toUserId");
if (!StringUtils.isEmpty(toUserId) && webSocketMap.containsKey(toUserId)) {
webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
} else {
log.error("請求的 userId:" + toUserId + "不在該服務(wù)器上");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 發(fā)生錯誤時調(diào)用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用戶錯誤:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
}
/**
* 實(shí)現(xiàn)服務(wù)器主動推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
public static synchronized AtomicInteger getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount.getAndIncrement();
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount.getAndDecrement();
}
}
其中,@ServerEndpoint("/websocket/{userId}")
表示讓 Spring 創(chuàng)建 WebSocket 的服務(wù)端點(diǎn),其中請求地址是?/websocket/{userId}
。
另外 WebSocket 一共有四個事件,分別對應(yīng) JSR-356 定義的?@OnOpen、@OnMessage、@OnClose、@OnError
?注解。
- @OnOpen:標(biāo)注客戶端打開 WebSocket 服務(wù)端點(diǎn)調(diào)用方法
- @OnClose:標(biāo)注客戶端關(guān)閉 WebSocket 服務(wù)端點(diǎn)調(diào)用方法
- @OnMessage:標(biāo)注客戶端發(fā)送消息,WebSocket 服務(wù)端點(diǎn)調(diào)用方法
- @OnError:標(biāo)注客戶端請求 WebSocket 服務(wù)端點(diǎn)發(fā)生異常調(diào)用方法
接下來啟動項(xiàng)目,使用 WebSocket 在線測試工具(http://www.easyswoole.com/wstool.html
)進(jìn)行測試,有能力的也可以自己寫個 html 測試。
打開網(wǎng)頁后,在服務(wù)地址中輸入ws://127.0.0.1:8088/websocket/xiaozhengwen
,點(diǎn)擊開啟連接
按鈕,消息記錄中會多一條由服務(wù)器端發(fā)送的連接成功!
記錄。
接下來再打開一個網(wǎng)頁,服務(wù)地址中輸入ws://127.0.0.1:8088/websocket/yangdandan
,點(diǎn)擊開啟連接
按鈕,然后回到第一次打開的網(wǎng)頁在消息框中輸入
{
? "fromUserId":"xiaozhengwen",
? "toUserId":"yangdandan",
? ?"msg":"吃過飯了嗎"
}
點(diǎn)擊發(fā)送到服務(wù)端
,第二個網(wǎng)頁中會收到服務(wù)端推送的消息
{
"fromUserId":"yangdandan",
"toUserId":"xiaozhengwen",
"msg":"吃過了"
}
?
?同樣,項(xiàng)目的日志中也會有相應(yīng)的日志:文章來源:http://www.zghlxwxcb.cn/news/detail-597236.html
2023-06-30 12:40:48.894 INFO 78908 --- [nio-8080-exec-1] com.wupx.server.WebSocketServer : 用戶連接:wupx,當(dāng)前在線人數(shù)為:1
2023-06-30 12:40:58.073 INFO 78908 --- [nio-8080-exec-2] com.wupx.server.WebSocketServer : 用戶連接:huxy,當(dāng)前在線人數(shù)為:2
2023-06-30 12:41:05.870 INFO 78908 --- [nio-8080-exec-3] com.wupx.server.WebSocketServer : 用戶消息:wupx,報文:{"toUserId":"huxy","message":"i love you"}
總結(jié)
? ? 本文簡單地介紹了 Spring Boot 集成 WebSocket 實(shí)現(xiàn)服務(wù)端主動推送消息到客戶端,是不是十分簡單呢?大家可以自己也寫個 demo 試試!文章來源地址http://www.zghlxwxcb.cn/news/detail-597236.html
到了這里,關(guān)于Spring Boot 集成 WebSocket 實(shí)現(xiàn)服務(wù)端推送消息到客戶端的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!