SpringBoot 集成websocket

springboot 版本爲 2.7,利用websocket向前端推送數據,配置如下;

1、添加依賴:

<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-websocket</artifactId>
</dependency>

項目中其他依賴自動安裝了 org.apache.tomcat.embed:tomcat-embed-websocket:9.0.26 依賴包,無需手動添加。如果你的項目中沒有這個依賴,則需要手動添加到 pom 中。

2、配置類中添加一個Bean

  @Bean
    WebSocketConfigurer createWebSocketConfigurer(@Autowired MessageHandler messageHandler, @Autowired MessageHandshakeInterceptor messageHandshakeInterceptor) {
        return new WebSocketConfigurer() {
            @Override
            public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
                registry.addHandler(messageHandler, "/websocket").addInterceptors(messageHandshakeInterceptor).setAllowedOrigins("*");
            }
        };
    }

其中自動注入了兩個對象: messageHandler 和  messageHandshakeInterceptor,其中 messageHandler  中用於消息的初始化、監聽、和連接的銷燬,messageHandshakeInterceptor 用於會話攔截,從httpSession中獲取用戶信息,包括登陸、權限相關。

3、messageHandler 對象

@Component
public class MessageHandler extends TextWebSocketHandler {
    //   保存所有會話實例
    private Map<String, WebSocketSession> clients = new ConcurrentHashMap<>();

    @Autowired
    Logger logger;

    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws IOException {
        //新會話放入Map
        clients.put(webSocketSession.getId(), webSocketSession);
        String httpSessionId = (String) webSocketSession.getAttributes().get("httpSessionId");
        logger.info(httpSessionId);
        logger.info("websocket: {} 連接成功", webSocketSession.getId());
        webSocketSession.sendMessage(new TextMessage("welcome"));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus status) {
        clients.remove(webSocketSession.getId());
        logger.info("websocket: {} 斷開連接", webSocketSession.getId());
    }

    public void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) {
        String s = message.getPayload();
        System.out.println(s);
    }
}

messageHandler 從 TextWebSocketHandler 繼承,因爲發送的是文本消息。如果是二進制消息,可以從 BinaryWebSocketHandler 繼承。覆寫的 afterConnectionEstablished 方法 和  afterConnectionClosed 方法,顧名思義,用於websocket連接建立和銷燬的回調。handleTextMessage 方法用於消息監聽。

如果是主動發送消息,可以從 clients 中保存的websocketSession 會話集合中,取出對應的 websocketSession,調用其 sendMessage方法,如果是羣發消息,則直接廣播數據即可。注意消息需要 TextMessage 包裝。

for (String id : clients.keySet()) {
    WebSocketSession session = clients.get(id);
    session.sendMessage(new TextMessage("welcome"));
}

問題來了,如果是給特定的客戶端發送消息,該如何區分客戶端?一般在登陸驗證後,將驗證信息保存到 httpSession中,websocketSession沒有用戶登陸信息,如果這時有一條消息過來,該發送給哪個 websocketSession?所以需要在websocketSession中拿到httpSession。這就是 messageHandshakeInterceptor 對象需要乾的事情。

4、messageHandshakeInterceptor 對象

@Component
public class MessageHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    public MessageHandshakeInterceptor () {
        super();
    }
}

添加了這個攔截器之後,就可以訪問httpSession中保存的信息。測試一下:

在httpSession中放入httpSessionId:

httpSession.setAttribute("httpSessionId", httpSession.getId());

在 websocket中拿到對應的 httpSessionId:

String httpSessionId = (String) webSocketSession.getAttributes().get("httpSessionId");
logger.info(httpSessionId);

 觀察 clients 中保存的key 是  websocketSession.getId() ,與 httpSeesion.getId() 是不同的。如果使用httpSeesion中的id作爲key,還可以直接從登陸會話中直接拿到對應參數,進而從clients 直接取出 websocketSession 會話,發送消息。

5、setAllowedOrigins 

如果直接使用上述方案同城websocket連接是會報錯的,原因在於瀏覽器對於 websocket的跨域。所以 setAllowedOrigins 非常重要。

registry.addHandler(messageHandler, "/websocket").addInterceptors(messageHandshakeInterceptor).setAllowedOrigins("*");

6、注意

這裏的websocket 與 http 服務公用了一個端口 80 或443。實際項目中使用nginx作爲反向代理,將請求發送到實際的內網服務器上。但是nginx默認情況下不會轉發ConnectionUpgrade首部(Header),導致WebSocket無法正常工作,nginx官方文檔裏已經說明了如何通過配置來解決。同時肯定會遇到連接60s後自動斷開的問題,因爲nginx默認60秒發現沒有數據傳送,就會關閉連接,可以通過proxy_read_timeout指令把這一時間延長。

 

參考鏈接:https://fookwood.com/spring-boot-tutorial-15-websocket#respond

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章