1. 程式人生 > >SpringBoot整合WebSocket【基於純H5】進行點對點[一對一]和廣播[一對多]實時推送

SpringBoot整合WebSocket【基於純H5】進行點對點[一對一]和廣播[一對多]實時推送

之前實現WebSocket基於STOMP的,覺得SpringBoot封裝的太高,不怎麼靈活,現在實現一個純H5的,也大概瞭解webSocket在內部是怎麼傳輸的。

1.環境搭建

因為在上一篇基於STOMP協議實現的WebSocket裡已經有大概介紹過Web的基本情況了,所以在這篇就不多說了,我們直接進入正題吧,在SpringBoot中,我們還是需要匯入WebSocket的包。

在pox.xml加上對springBoot對WebSocket的支援:

<!-- webSocket -->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-websocket</artifactId>  
</dependency> 

這裡大概說一下自己的一點小見解:客戶端與伺服器建立WebSocket連線,實際上是建立了一個Socket,這個Socket是共享與客戶端和伺服器的。兩者只要往對應的Socket裡操作,就可以實現雙方實時通訊了

2.編碼實現

一、在SpringBoot中,新增WebSocket的配置

package com.cloud.sbjm.configure;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import com.cloud.sbjm.security.WebSocketInterceptor;
import com.cloud.sbjm.service.Imp.MyHandler;


//實現介面來配置Websocket請求的路徑和攔截器。
@Configuration
@EnableWebSocket
public class WebSocketH5Config implements WebSocketConfigurer{

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		
	    //handler是webSocket的核心,配置入口
	    registry.addHandler(new MyHandler(), "/myHandler/{ID}").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
		
	}

	
}

1.@Configuration:註解標識該類為Spring的配置類

2.@EnableWebSocket:開啟註解接收和傳送訊息

3.實現WebSocketConfigurer介面,重寫registerWebSocketHandlers方法,這是一個核心實現方法,配置websocket入口,允許訪問的域、註冊Handler、定義攔截器。客戶端通過“/myHandler/{ID}”直接訪問Handler核心類,進行socket的連線、接收、傳送等操作,這裡由於還加了個攔截器,所以建立新的socket訪問時,都先進來攔截器再進去Handler類,“new WebSocketInterceptor()”是我實現的攔截器,“new MyHandler()”是我實現的一個Handler類。

二、WebSocketInterceptor攔截器的實現:

package com.cloud.sbjm.security;

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

public class WebSocketInterceptor implements HandshakeInterceptor {

    //進入hander之前的攔截
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            String ID = request.getURI().toString().split("ID=")[1];
            System.out.println("當前session的ID="+ID);
            ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
            HttpSession session = serverHttpRequest.getServletRequest().getSession();
            map.put("WEBSOCKET_USERID",ID);
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
    	System.out.println("進來webSocket的afterHandshake攔截器!");
    }
}

1.實現了HandshakeInterceptor 介面,並實現了beforeHandshake該方法,該方法是在進入Handler核心類之前進行攔截。

這裡主要實現的邏輯是:

擷取客戶端建立webSocket連線時傳送的URL地址字串,並通過對該字串進行特殊標識擷取操作,獲取客戶端傳送的唯一標識(由自己定義的,一般是系統使用者ID唯一標識,用以標識該使用者),並把它以鍵值對的形式放到Session裡,這樣後期可以通過該session獲取它對應的使用者ID了。【一個session對應著一個webSocketSession】

三、MyHandler核心類的實現

package com.cloud.sbjm.service.Imp;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;


@Service
public class MyHandler implements WebSocketHandler {
	
    //線上使用者列表
    private static final Map<String, WebSocketSession> users;

   
    static {
        users = new HashMap<>();
    }
    //新增socket
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    	 System.out.println("成功建立連線");
    	 String ID = session.getUri().toString().split("ID=")[1];
         System.out.println(ID);
         if (ID != null) {
             users.put(ID, session);
             session.sendMessage(new TextMessage("成功建立socket連線"));
             System.out.println(ID);
             System.out.println(session);
         }
         System.out.println("當前線上人數:"+users.size());
    }

    //接收socket資訊
    @Override
	public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
    	try{
	    	JSONObject jsonobject = JSONObject.fromObject(webSocketMessage.getPayload());    	
	    	System.out.println(jsonobject.get("id"));
	    	System.out.println(jsonobject.get("message")+":來自"+(String)webSocketSession.getAttributes().get("WEBSOCKET_USERID")+"的訊息");
	    	sendMessageToUser(jsonobject.get("id")+"",new TextMessage("伺服器收到了,hello!"));
    	 }catch(Exception e){
      	   e.printStackTrace();
         }
		
	}

    /**
     * 傳送資訊給指定使用者
     * @param clientId
     * @param message
     * @return
     */
    public boolean sendMessageToUser(String clientId, TextMessage message) {
        if (users.get(clientId) == null) return false;
        WebSocketSession session = users.get(clientId);
        System.out.println("sendMessage:" + session);
        if (!session.isOpen()) return false;
        try {
            session.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 廣播資訊
     * @param message
     * @return
     */
    public boolean sendMessageToAllUsers(TextMessage message) {
        boolean allSendSuccess = true;
        Set<String> clientIds = users.keySet();
        WebSocketSession session = null;
        for (String clientId : clientIds) {
            try {
                session = users.get(clientId);
                if (session.isOpen()) {
                    session.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
                allSendSuccess = false;
            }
        }

        return  allSendSuccess;
    }


    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if (session.isOpen()) {
            session.close();
        }
        System.out.println("連接出錯");
        users.remove(getClientId(session));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("連線已關閉:" + status);
        users.remove(getClientId(session));
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 獲取使用者標識
     * @param session
     * @return
     */
    private Integer getClientId(WebSocketSession session) {
        try {
            Integer clientId = (Integer) session.getAttributes().get("WEBSOCKET_USERID");
            return clientId;
        } catch (Exception e) {
            return null;
        }
    }
}

1.實現了WebSocketHandler介面,並實現了關鍵的幾個方法。

① afterConnectionEstablished(介面提供的):建立新的socket連線後回撥的方法。主要邏輯是:將成功建立連線的webSocketSssion放到定義好的常量[private static final Map<String, WebSocketSession> users;]中去。這裡也擷取客戶端訪問的URL的字串,拿到標識,以鍵值對的形式講每一個webSocketSession存到users裡,以記錄每個Socket。

② handleMessage(介面提供的):接收客戶端傳送的Socket。主要邏輯是:獲取客戶端傳送的資訊。這裡之所以可以獲取本次Socket的ID,是因為客戶端在第一次進行連線時,攔截器進行攔截後,設定好ID,這樣也說明,雙方在相互通訊的時候,只是對第一次建立好的socket持續進行操作。

③ sendMessageToUser(自己定義的):傳送給指定使用者資訊。主要邏輯是:根據使用者ID從常量users(記錄每一個Socket)中,獲取Socket,往該Socket裡傳送訊息,只要客戶端還線上,就能收到該訊息。

④sendMessageToAllUsers (自己定義的):這個廣播訊息,傳送資訊給所有socket。主要邏輯是:跟③型別,只不過是遍歷整個users獲取每一個socket,給每一個socket傳送訊息即可完廣播發送

⑤handleTransportError(介面提供的):連接出錯時,回撥的方法。主要邏輯是:一旦有連接出錯的Socket,就從users裡進行移除,有提供該Socket的引數,可直接獲取ID,進行移除。這個在客戶端沒有正常關閉連線時,會進來,所以在開發客戶端時,記得關閉連線

⑥afterConnectionClosed(介面提供的):連線關閉時,回撥的方法。主要邏輯:一旦客戶端/伺服器主動關閉連線時,將個socket從users裡移除,有提供該Socket的引數,可直接獲取ID,進行移除。

後臺的開發就開發完了,大家有沒有發現比基於STOMP協議實現要靈活得多

四、客戶端頁面的實現【基於H5】

不需要加入任何的JS包

<!DOCTYPE html>
<html>
  <head>
    <title>socket.html</title>
	
    <meta name="keywords" content="keyword1,keyword2,keyword3">
    <meta name="description" content="this is my page">
    <meta name="content-type" content="text/html" charset="UTF-8">
    <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->

  </head>
  
  <body>
	
	Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button>    <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
    <!-- 公共JS -->
    <script type="text/javascript" src="../webSocket/jquery.min.js"></script>

	
<script type="text/javascript">
var userID="888";
var websocket=null;
 $(function() {
  
  //建立WebSocket
  connectWebSocket();
  })
  
   //強制關閉瀏覽器  呼叫websocket.close(),進行正常關閉
 window.onunload = function() {
   	 
   //關閉連線   
   closeWebSocket();    
    }
  
  //建立WebSocket連線
  function connectWebSocket(){
  	
	console.log("開始...");
      
      //建立webSocket連線
       websocket = new WebSocket("ws://127.0.0.1:9091/cloud-sbjm/myHandler/ID="+userID);
      
      //開啟webSokcet連線時,回撥該函式
       websocket.onopen = function () {      
        console.log("onpen");  
       }
       
       //關閉webSocket連線時,回撥該函式
       websocket.onclose = function () {
       //關閉連線    
        console.log("onclose");
       }
	
	   //接收資訊
       websocket.onmessage = function (msg) {
        console.log(msg.data);
       }
  }
  
  //傳送訊息
  function send(){
     var postValue={};
     postValue.id=userID;
     postValue.message=$("#text").val(); 	 	
     websocket.send(JSON.stringify(postValue));
  }
  //關閉連線
  function closeWebSocket(){
  	 if(websocket != null) {
            websocket.close();
        }
  }

</script>
  </body>
</html>

頁面比較簡單,簡單解釋一下:

1.new WebSocket("ws://127.0.0.1:9091/cloud-sbjm/myHandler/ID="+userID),與伺服器建立webSocket連線,後面的ID="+userID,是動態引數,跟伺服器配置Handler的訪問地址時對應"/myHandler/{ID}"。

2.H5也提供多個回撥函式

onopen:開啟webSokcet連線時,回撥該函式

onclose:關閉webSocket連線時,回撥該函式

onmessage:伺服器給該socket傳送訊息時,回撥該函式,獲取訊息

websocket.send(JSON.stringify(postValue));:給Socket傳送訊息,伺服器獲取

websocket.close();客戶端主要關閉連線,會觸發客戶端的onclose方法和伺服器的afterConnectionClosed方法

到此服務端的開發也完成了,下面執行一下程式效果圖:

一、建立連線

客戶端:

伺服器:

二、傳送訊息

客戶端:

伺服器:

三、伺服器主動推送訊息

伺服器程式碼:

到此已經完成了,各位可以根據自己需求進行修改,這會靈活多了!

如有錯漏,請各位指導。