1. 程式人生 > 程式設計 >SpringBoot整合WebSocket長連線實際應用詳解

SpringBoot整合WebSocket長連線實際應用詳解

前言:

一、WebSocket之初出茅驢

官方定義:WebSocket是一種在單個TCP連線上進行全雙工通訊的協議。WebSocket使得客戶端和伺服器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料。在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。是真正的雙向平等對話,屬於伺服器推送技術的一種。

太官方啦,還是博主過來翻譯一下吧 :WebSocket技術只需要service和client建立一次連線,就能實現伺服器和客戶端雙方相互頻繁的傳送請求和通訊!(簡單加粗暴的翻譯有木有,哈哈!)

WebSocket經典的使用場景:網站線上聊天系統、彈幕系統…

臣附議:webSocket技術無法做到向下相容,不相容低版本的IE,因此依賴於瀏覽器版本,這也正是webSocket非常顯著的缺陷。

二、WebSocket的出現到底為我們解決了什麼實際問題?

在傳統的b/s架構中,要實現伺服器向client進行實時訊息推送功能,市場上常用的解決方案大致分為三類:

定時輪詢 客戶端以一定的時間間隔向服務端發出請求
長輪詢 如果伺服器沒有可以立即返回給客戶端的資料,則不會立刻返回一個空結果
流技術 客戶端隱藏的視窗向服務端發出一個長輪詢的請求

長輪詢機制:

SpringBoot整合WebSocket長連線實際應用詳解

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

SpringBoot整合WebSocket長連線實際應用詳解

三、博主使用WebSocket的場景

博主最進在公司呼叫第三方影像採集系統,由於影像狀態是非同步返回給業務系統的,導致當業務系統收到第三方回撥後,對於前臺使用者體驗來說是無感知的,因此前臺必須重新整理頁面才能獲取到影像最新狀態。這時候由service主動向client實時傳送影像採集狀態的通知是最好不過的方案!在上述提到的常用解決方案,像輪詢這種比較low的實現,博主作為技術宅,肯定是不會作為技術選型的,哈哈…

四、不多bb,上程式碼!

本專案是基於SpringBoot環境開發

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 );
  }

  /**
   * 向所有線上人傳送訊息
   * @param message
   */
  public static void sendMessageForAll(String message) {
    //jdk8 新方法
    ONLINE_SESSION.forEach((sessionId,session) -> sendMessage(session,message));
  }
}

3、 WebSocketController

如上,已經建立好了簡單的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長連線內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!