SpringBoot+WebSocket實現消息推送功能

背景

項目中經常會用到消息推送功能,關於推送技術的實現,我們通常會聯想到輪詢、comet長連接技術,雖然這些技術能夠實現,但是需要反復連接,對於服務資源消耗過大,隨著技術的發展,HtML5定義瞭WebSocket協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。本文將介紹如何采用websocket實現消息推送。

WebSocket簡介

WebSocket協議是基於TCP的一種新的網絡協議。它實現瞭瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。瀏覽器和服務器僅需一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

協議原理

Websocket協議基於Http協議,針對Http協議進行瞭相關的改善,且Websocket協議也需要建立TCP連接來實現數據傳輸,具體實現如下圖:

說明:

  • 客戶端發起http請求,經過3次握手後,建立起TCP連接;http請求裡存放WebSocket支持的版本號等信息,如:Upgrade、Connection、WebSocket-Version等。
  • 服務器收到客戶端的握手請求後,同樣采用HTTP協議回饋數據
  • 客戶端收到連接成功的消息後,開始借助於TCP傳輸信道進行全雙工通信.

WebSocket與HTTP協議的區別

相同點:都是一樣基於TCP的,都是可靠性傳輸協議。都是應用層協議。

不同點:

  • WebSocket是雙向通信協議,可以雙向發送或接受信息,而HTTP是單向協議
  • WebSocket需要瀏覽器和服務器握手進行建立連接的,而http是瀏覽器發起向服務器的連接。

WebSocket特點

  • 建立在TCP協議之上,服務器端的實現比較容易。
  • 數據格式比較輕量,性能開銷小,通信高效。
  • 支持多種數據格式,可以發送文本、二進制數據。
  • 客戶端可以與任意服務器通信,無同源限制。

應用場景

  • 即時聊天通信
  • 在線協同編輯/編輯
  • 實時數據流的拉取與推送
  • 實時地圖位置

系統集成Websocket

jar包引入

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.4.RELEASE</version>
    <relativePath/>
 </parent>
 
  <dependency>
      <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-websocket</artifactId>
  </dependency>

Websocket配置

@Configuration
public class WebSocketConfig
{
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

具體實現

@ServerEndpoint(value="/websocket/{uid}")
@Component
public class WebSocketServer
{
    private Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
    
    private static final AtomicInteger onlineCount = new AtomicInteger(0);
    
    private static CopyOnWriteArraySet<Session> sessionSet = new CopyOnWriteArraySet<Session>();
    
    @OnOpen
    public void onOpen(Session session,@PathParam("uid") String uid) 
    {
        logger.info("open message uid:{}",uid);
        sessionSet.add(session);
        onlineCount.incrementAndGet();
        logger.info("窗口開始監聽uid:{},當前在線人數:{}",uid,onlineCount);
    }
    
    @OnClose
    public void onClose(Session session) 
    {
        String sessionId=session.getId();
        logger.info("sessionid:{} close",sessionId);
        sessionSet.remove(this);
        int count=onlineCount.decrementAndGet();
        logger.info("有一連接關閉!當前在線人數為:{}",count);
    }

    @OnError
    public void onError(Session session, Throwable error) 
    {
        logger.error("消息發生錯誤:{},Session ID: {}",error.getMessage(),session.getId());
    }
    
   
    public void batchSendMesasge(String uid,String message) throws IOException 
    {
        logger.info("推送消息到窗口:{},推送內容:{}",uid,message);
        for(Session session:sessionSet){
            sendMessage(session, message);
        }
    }
    
    public void sendMessage(Session session, String message) throws IOException {
        
        if(session!=null)
        {
            synchronized (session) {
                session.getBasicRemote().sendText(message);
            }
        }
    }

}

說明: @OnOpen :當有新的WebSocket連接進入時調用 @OnClose:當有WebSocket連接關閉時調用 @OnError :當有WebSocket拋出異常時調用 @OnMessage:當接收到字符串消息時,對該方法進行回調

測試示例

@Controller
public class WebScoketController
{
   @Autowired
   private WebSocketServer webSocketServer;
  
    @ResponseBody
    @RequestMapping("/sendMessage")
    public String batchMessage(String uid,String message) 
    {  
        Map<String, String> map =new HashMap<String, String>();
        try 
        {
            map.put("code", "200");
            webSocketServer.batchSendMesasge(uid,message);
        } 
        catch (Exception e) 
        {
            map.put("code", "-1");
            map.put("message", e.getMessage());
        }
        return JSON.toJSONString(map);
    } 
    
    @GetMapping("/enter")
    public String enter() 
    { 
        return "webscoketTest.html";
    }
}

頁面請求websocket

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>websocket test</title>
    
      <script type="text/javascript">
            if ("WebSocket" in window)
            {
            	console.log("您的瀏覽器支持 WebSocket!");
               
               var ws = new WebSocket("ws://127.0.0.1:9092/websocket/1234");
               console.log('ws連接狀態:' + ws.readyState);
               
               //打開
               ws.onopen = function()
               {
                  ws.send("message test");
                  console.log("mesage sending");
               };
                
                //發送消息
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert(received_msg);
               };
                
                //關閉
               ws.onclose = function()
               { 
                  // 關閉 websocket
                  console.log("socket is close"); 
               };
            }
            
            else
            {
            	 console.log("您的瀏覽器不支持 WebSocket!");
            }
      </script>
   </head>
</html>

測試效果

啟動程序:運行http://localhost:9092/enter 進入頁面開啟websocket。

用戶發送消息:http://localhost:9092/sendMessage?uid=1235&message=this%20is%20message1

執行的結果如下:

 open message uid:1234
 [nio-9092-exec-2] c.s.f.w.controller.WebSocketServer: 窗口開始監聽uid:1234,當前在線人數:1
 [nio-9092-exec-5] c.s.f.w.controller.WebSocketServer: 推送消息到窗口:1234,推送內容:this is message

以上就是SpringBoot+WebSocket實現消息推送功能的詳細內容,更多關於SpringBoot WebSocket消息推送的資料請關註WalkonNet其它相關文章!

推薦閱讀: