SpringBoot集成WebSocket長連接實際應用詳解
前言:
一、WebSocket之初出茅驢
官方定義:WebSocket是一種在單個TCP連接上進行全雙工通信的協議。WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進行雙向數據傳輸。是真正的雙向平等對話,屬于服務器推送技術的一種。
太官方啦,還是博主過來翻譯一下吧 :WebSocket技術只需要service和client建立一次連接,就能實現服務器和客戶端雙方相互頻繁的發(fā)送請求和通信?。ê唵渭哟直┑姆g有木有,哈哈?。?/p>
WebSocket經典的使用場景:網站在線聊天系統(tǒng)、彈幕系統(tǒng)…
臣附議:webSocket技術無法做到向下兼容,不兼容低版本的IE,因此依賴于瀏覽器版本,這也正是webSocket非常顯著的缺陷。
二、WebSocket的出現到底為我們解決了什么實際問題?
在傳統(tǒng)的b/s架構中,要實現服務器向client進行實時消息推送功能,市場上常用的解決方案大致分為三類:
| 定時輪詢 | 客戶端以一定的時間間隔向服務端發(fā)出請求 |
|---|---|
| 長輪詢 | 如果服務器沒有可以立即返回給客戶端的數據,則不會立刻返回一個空結果 |
| 流技術 | 客戶端隱藏的窗口向服務端發(fā)出一個長輪詢的請求 |
長輪詢機制:

綜合這幾種方案,您會發(fā)現這些目前我們所使用的所謂的實時技術并不是真正的實時技術,它們只是在用 Ajax 方式來模擬實時的效果,定時輪詢需要實時獲取取服務端信息的應用時, client需要頻繁輪詢server,為了拿到最新信息, client一直這樣循環(huán)下去server如果一直沒有新的消息, client的大多請求都是屬于無效請求, 導致會帶來很多無謂的網絡傳輸,所以這是一種非常低效的實時方案。長輪詢對服務器造成的壓力非常大,并且如果服務端的數據變更非常頻繁的話,這種方式無異于定時輪詢。所以為了解決傳統(tǒng)http請求的實際問題,WebSocket技術應運而生!下面博主給張圖讓大家生動的理解傳統(tǒng)HTTP和WebSocket的差異化:

三、博主使用WebSocket的場景
博主最進在公司調用第三方影像采集系統(tǒng),由于影像狀態(tài)是異步返回給業(yè)務系統(tǒng)的,導致當業(yè)務系統(tǒng)收到第三方回調后,對于前臺用戶體驗來說是無感知的,因此前臺必須刷新頁面才能獲取到影像最新狀態(tài)。這時候由service主動向client實時發(fā)送影像采集狀態(tài)的通知是最好不過的方案!在上述提到的常用解決方案,像輪詢這種比較low的實現,博主作為技術宅,肯定是不會作為技術選型的,哈哈…
四、不多bb,上代碼!
本項目是基于SpringBoot環(huán)境開發(fā)
1、導入websocket坐標
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2、封裝WebSocketUtil工具類,用于提供對session鏈接、斷開連接、推送消息的簡單控制。
public class WebsocketUtil {
/**
* 記錄當前在線的Session
*/
private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<> ();
/**
* 添加session
* @param userId
* @param session
*/
public static void addSession(String userId, Session session){
// 此處只允許一個用戶的session鏈接。一個用戶的多個連接,我們視為無效。
ONLINE_SESSION.putIfAbsent ( userId, session );
}
/**
* 關閉session
* @param userId
*/
public static void removeSession(String userId){
ONLINE_SESSION.remove ( userId );
}
/**
* 給單個用戶推送消息
* @param session
* @param message
*/
public static void sendMessage(Session session, String message){
if(session == null){
return;
}
// 同步
RemoteEndpoint.Async async = session.getAsyncRemote ();
async.sendText ( message );
}
/**
* 向所有在線人發(fā)送消息
* @param message
*/
public static void sendMessageForAll(String message) {
//jdk8 新方法
ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));
}
}
3、 WebSocketController
如上,已經創(chuàng)建好了簡單的session管理和消息管理,接下來要使用注解的方式,使用SpringBoot的websocket包提供的方法,實現OnOpen、OnClose、OnMessage三個方法,再實現一個OnError方法來應對異常。代碼段如下:
/**
* websocket接口處理類
*/
@Component
@ServerEndpoint ( value = "/chat/{userid}" )
public class WebsocketController {
/**
* 連接事件,加入注解
* @param userId
* @param session
*/
@OnOpen
public void onOpen( @PathParam ( value = "userid" ) String userId, Session session ) {
String message ="[" + userId + "]加入聊天室!!";
// 添加到session的映射關系中
WebsocketUtil.addSession ( userId, session );
// 廣播通知,某用戶上線了
WebsocketUtil.sendMessageForAll ( message );
}
/**
* 連接事件,加入注解
* 用戶斷開鏈接
* @param userId
* @param session
*/
@OnClose
public void onClose(@PathParam ( value = "userId" ) String userId, Session session ) {
String message ="[" + userId + "]退出了聊天室...";
// 刪除映射關系
WebsocketUtil.removeSession ( userId );
// 廣播通知,用戶下線了
WebsocketUtil.sendMessageForAll ( message );
}
/**
* 當接收到用戶上傳的消息
* @param userId
* @param session
*/
@OnMessage
public void onMessage(@PathParam ( value = "userId" ) String userId, Session session ,String message) {
String msg ="[" + userId + "]:"+message;
// 直接廣播
WebsocketUtil.sendMessageForAll ( msg );
}
/**
* 處理用戶活連接異常
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
throwable.printStackTrace();
}
}
4、添加 ServerEndpointExporter 啟動Bean
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
/**
* 會自動注冊使用了@ServerEndpoint注解聲明的Websocket endpoint
* 要注意,如果使用獨立的servlet容器,
* 而不是直接使用springboot的內置容器,
* 就不要注入ServerEndpointExporter,因為它將由容器自己提供和管理。
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
那些年踩過的坑:
注意:在websocketEndpoint中,使用@Autowired一些列注解注入Bean時候,一直無法注入,報空指針。原因在于spring管理的都是單例(singleton),和 websocket (多對象)相沖突。
解決辦法:通過上下文獲取bean實例:從Spring上下文獲取bean實例的方法
到此這篇關于SpringBoot集成WebSocket長連接實際應用詳解的文章就介紹到這了,更多相關SpringBoot WebSocket長連接內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

