切換語言為:簡體
Tomcat 中的 WebSocket 與 Http的區別以及實現方式

Tomcat 中的 WebSocket 與 Http的區別以及實現方式

  • 爱糖宝
  • 2024-06-04
  • 2087
  • 0
  • 0

WebSocket是一種在客戶端和伺服器之間提供長期、雙向、實時通訊的協議

全雙工通訊:WebSocket允許資料同時在客戶端和伺服器雙向通訊,無需像HTTP等待請求和響應的迴圈

單個TCP連線:建立一次連線後,雙方可在持久連線上交換任意數量的資料包,減少網路延遲、資源消耗

升級協議:WebSocket連線初始化時,透過HTTP協議進行一次握手,之後便升級到WebSocket協議進行數據傳輸

事件驅動:WebSocket通訊基於事件,如 OnOpenOnMessageOnClose

WebSocket快速入門

SrpingBoot專案整合WebSocket

匯入maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-WebSocket</artifactId>
    <version>3.0.4</version>
</dependency>

新建一個配置類 @Configuration,將 ServerEndpointExporter 加入容器

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

新建一個類,使用註解@ServerEndpoint標識路徑,並使用註解@Component加入容器

有一系列@OnXX的註解可以標識在方法上,表示當遇到XX情況時呼叫對應的方法

@OnOpen 建立連線、@OnClose 關閉連線、@OnMessage 收到訊息、@OnError 出現錯誤

@ServerEndpoint("/ws/{id}")
@Component
public class WebSocketServer {

    private static final Map<Long, Session> map = new ConcurrentHashMap<>();

    @OnOpen
    public void open(@PathParam("id") Long id, Session session) {
        map.put(id, session);
        System.out.println(id + " 建立連線");
    }

    @OnClose
    public void close(@PathParam("id") Long id) {
        map.remove(id);
        System.out.println(id + " 關閉連線");
    }

    @OnMessage
    public void msg(String msg, Session session) {
        session.getAsyncRemote().sendText("收到訊息:" + msg);
    }

    @OnError
    public void error(String error) {
        System.out.println(error);
    }

}

注意:open、msg方法中的入參Session是WebSocket中的,而不是servlet規範的

配置的埠為8080,context path為/caicai

server:
  port: 8080
  servlet:
    context-path: /caicai

接下來就可以開始測試了,使用ApiFox工具建立WebSocket連線,傳送訊息111,最終會呼叫msg方法傳送:收到訊息:111

Tomcat 中的 WebSocket 與 Http的區別以及實現方式

WebSocket原理

我們在配置類中將ServerEndpointExporter類加入容器

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

ServerEndpointExporter實現SmartInitializingSingleton介面,在容器初始化完Bean後,呼叫afterSingletonsInstantiated方法

@Override
public void afterSingletonsInstantiated() {
    registerEndpoints();
}

也就是單例Bean例項化之後執行,會掃描容器中的WebSocket處理類並註冊

protected void registerEndpoints() {
    //收集WebSocket處理類
    Set<Class<?>> endpointClasses = new LinkedHashSet<>();
    if (this.annotatedEndpointClasses != null) {
       endpointClasses.addAll(this.annotatedEndpointClasses);
    }

    ApplicationContext context = getApplicationContext();
    if (context != null) {
       //從容器中找到@ServerEndpoint註解標識的WebSocket處理類
       String[] endpointBeanNames = context.getBeanNamesForAnnotation(ServerEndpoint.class);
       for (String beanName : endpointBeanNames) {
          endpointClasses.add(context.getType(beanName));
       }
    }
	//註冊
    for (Class<?> endpointClass : endpointClasses) {
       registerEndpoint(endpointClass);
    }

    if (context != null) {
       Map<String, ServerEndpointConfig> endpointConfigMap = context.getBeansOfType(ServerEndpointConfig.class);
       for (ServerEndpointConfig endpointConfig : endpointConfigMap.values()) {
          registerEndpoint(endpointConfig);
       }
    }
}

最終加入WebSocketContainer容器(ServerContainer extends WebSocketContainer)

private void registerEndpoint(Class<?> endpointClass) {
    ServerContainer serverContainer = getServerContainer();
    Assert.state(serverContainer != null,
          "No ServerContainer set. Most likely the server's own WebSocket ServletContainerInitializer " +
          "has not run yet. Was the Spring ApplicationContext refreshed through a " +
          "org.springframework.web.context.ContextLoaderListener, " +
          "i.e. after the ServletContext has been fully initialized?");
    try {
       if (logger.isDebugEnabled()) {
          logger.debug("Registering @ServerEndpoint class: " + endpointClass);
       }
       //加入容器 
       serverContainer.addEndpoint(endpointClass);
    }
    catch (DeploymentException ex) {
       throw new IllegalStateException("Failed to register @ServerEndpoint class: " + endpointClass, ex);
    }
}

ServerEndpointExporter在容器啟用時,掃描容器中被@ServerEndpoint標識的WebSocket處理類並註冊

在前文曾說過:請求由EndPoint進行網路通訊,當處理完網路通訊封裝成SocketProcessorBase交給執行緒池進行執行,會先呼叫Http11Processor解析再呼叫Adapter介面卡交給容器處理

Tomcat 中的 WebSocket 與 Http的區別以及實現方式

作為升級協議的WebSocket前面網路通訊流程不變,而呼叫Processor時會使用UpgradeProcessorInternal

UpgradeProcessorInternal最終會找到WebSocketContainer容器中對應的WebSocket處理類對應的方法進行呼叫(不會打到Container容器)

Tomcat 中的 WebSocket 與 Http的區別以及實現方式

總結

WebSocket是一種長期、雙向、實時通訊的協議,基於HTTP協議後升級為WebSocket協議

Tomcat在處理WebSocket時與HTTP請求有所不同,處理網路通訊依舊還是使用EndPoint

當請求為HTTP時會使用Http11Processor接卸請求,經過介面卡最終交給Container容器處理;當請求為WebSocket時使用UpgradeProcessorInternal,路由到WebSocketContainer容器中的ServerEndPoint處理類進行處理

ServerEndpointExporter實現SmartInitializingSingleton介面,在bean例項化後找到容器中被註解ServerEndPoint標識的處理類加入WebSocketContainer容器

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.