Tomcat中的WebSocket是如何实现的?

简介: 【10月更文挑战第7天】本文介绍了WebSocket在Tomcat中的实现,包括其全双工通信、单个TCP连接、协议升级和事件驱动的特点。通过Spring Boot项目整合WebSocket,展示了如何配置依赖、创建WebSocket处理类和配置类。详细解析了WebSocket的原理,包括ServerEndpointExporter的注册过程和请求处理流程。总结了WebSocket与HTTP请求处理的区别,并提供了进一步学习的资源。

Tomcat中的WebSocket是如何实现的?

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

image.png

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适配器交给容器处理

image.png

作为升级协议的WebSocket前面网络通信流程不变,而调用Processor时会使用UpgradeProcessorInternal

UpgradeProcessorInternal最终会找到WebSocketContainer容器中对应的WebSocket处理类对应的方法进行调用(不会打到Container容器)

image.png

总结

WebSocket是一种长期、双向、实时通信的协议,基于HTTP协议后升级为WebSocket协议

Tomcat在处理WebSocket时与HTTP请求有所不同,处理网络通信依旧还是使用EndPoint

当请求为HTTP时会使用Http11Processor接卸请求,经过适配器最终交给Container容器处理;当请求为WebSocket时使用UpgradeProcessorInternal,路由到WebSocketContainer容器中的ServerEndPoint处理类进行处理

ServerEndpointExporter实现SmartInitializingSingleton接口,在bean实例化后找到容器中被注解ServerEndPoint标识的处理类加入WebSocketContainer容器

🌠最后(不要白嫖,一键三连求求拉~)

本篇文章被收入专栏 Tomcat全解析:架构设计与核心组件实现,感兴趣的同学可以持续关注喔

本篇文章笔记以及案例被收入 Gitee-CaiCaiJavaGithub-CaiCaiJava,除此之外还有更多Java进阶相关知识,感兴趣的同学可以starred持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多技术干货,公众号:菜菜的后端私房菜

相关文章
|
6月前
|
Web App开发 移动开发 Java
基于tomcat运行HTML5 WebSocket echo例子
基于tomcat运行HTML5 WebSocket echo例子
55 2
|
应用服务中间件 Linux Apache
记录一次奇葩的websocket和tomcat7.0.75的bug
本地用的tomcat7.0.57,websocket消息推送一切正常,windows服务器用的也是tomcat7.0.57 也一切正常。 centos上用的tomcat 7.0.75,webscoket的绝大部分都正常,就是用户上线的消息推送不过去,非常诡异,本地调试了半天,搜了很多博客也没找到原因,后来发现唯一的区别就是centos上的tomcat版本高了一点,但是不应该低版本的能正常
1558 0
|
Web App开发 Java 应用服务中间件
Java后端WebSocket的Tomcat实现
文章摘要随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。
1828 0
|
应用服务中间件
Tomcat如何实现WebSocket
WebSocket协议属于HTML5标准,越来越多浏览器已经原生支持WebSocket,它能让客户端和服务端实现双向通信。
1278 0
|
Web App开发 移动开发 应用服务中间件
基于tomcat运行HTML5 WebSocket echo例子
一:概述 作为HTML5新特性之一的WebSocket组件,在实时性有一定要求的WEB应用开发中还是有一定用武之地,高版本的IE、Chrome、FF浏览器都支持Websocket,标准的Websocket通信是基于RFC6455实现服务器端与客户端握手与消息接发的。
1216 0
|
应用服务中间件 网络协议
Tomcat 7的WebSocket实现(下)
版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 https://blog.csdn.net/chszs/article/details/20153123 Tomcat 7的WebSocket实现(下) 作者:chszs,转载需注明。
736 0
|
应用服务中间件 Apache 容器
Tomcat 7的WebSocket实现(上)
Tomcat 7的WebSocket实现(上) 本文覆盖了以下内容: 1)Web通信的演进 2)WebSocket 3)WebSocket在Apache Tomcat 7的实现 4)怎样用Jaggery开发WebSocket特性 Tomcat 7中引入了WebSocket实现。
1321 0