WebSocket是一種在客戶端和伺服器之間提供長期、雙向、實時通訊的協議
全雙工通訊:WebSocket允許資料同時在客戶端和伺服器雙向通訊,無需像HTTP等待請求和響應的迴圈
單個TCP連線:建立一次連線後,雙方可在持久連線上交換任意數量的資料包,減少網路延遲、資源消耗
升級協議:WebSocket連線初始化時,透過HTTP協議進行一次握手,之後便升級到WebSocket協議進行數據傳輸
事件驅動:WebSocket通訊基於事件,如 OnOpen
、OnMessage
、OnClose
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
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介面卡交給容器處理
作為升級協議的WebSocket前面網路通訊流程不變,而呼叫Processor時會使用UpgradeProcessorInternal
UpgradeProcessorInternal最終會找到WebSocketContainer容器中對應的WebSocket處理類對應的方法進行呼叫(不會打到Container容器)
總結
WebSocket是一種長期、雙向、實時通訊的協議,基於HTTP協議後升級為WebSocket協議
Tomcat在處理WebSocket時與HTTP請求有所不同,處理網路通訊依舊還是使用EndPoint
當請求為HTTP時會使用Http11Processor接卸請求,經過介面卡最終交給Container容器處理;當請求為WebSocket時使用UpgradeProcessorInternal,路由到WebSocketContainer容器中的ServerEndPoint處理類進行處理
ServerEndpointExporter實現SmartInitializingSingleton介面,在bean例項化後找到容器中被註解ServerEndPoint標識的處理類加入WebSocketContainer容器