Spring整合tomcat的WebSocket详细逻辑(图解)
主要解决存在的疑问
- 为什么存在2种spring整合websocket的方式,一种是使用@ServerEndpoint注解的方式,一种是使用@EnableWebSocket注解的方式,这2种有什么区别和联系?可以共存吗?它们实现的原理是什么?它们的各种配置到底是什么意思?@EnableWebSocket是如何完成配置的?
- 在服务端和客户端建立websocket连接的时候,如何做认证?不能让任意的客户端连接到websocket服务端,而且不应该在建立websocket连接之后再认证,而是应该在握手的时候,就去做认证,该如何实现?
spring、tomcat是如何配合完成websocket
这篇文章觉得不错,原文地址:spring、tomcat是如何配合完成websocket
综述
像IM这一类web系统,需要有机制知道是否有新消息,没有websocket前主要靠轮训。
轮训频率设得过高,有效轮训率低,不仅消耗网络资源,还占用cpu资源;轮训频率设得过低,又会造成消息延时较大。
为此诞生了websocket,消息可以由服务端主动推送到客户端,不仅实时性高,效率也是拉满。
为了尽量减少对现有系统进行改造,websocket是在建立在http的基础之上的,这样不仅可以复用http的端口,服务端及客户端的改造都更小。
- 握手阶段:如上所述websocket是基于http,主要是握手阶段仍使用http协议,当HTTP处理器发现头部带“Upgrade: websocket”,则认为连接是要升级到websocket的协议;如果支持websocket则返回允许升级到websocket的响应。
- 通信阶段:客户端接受到服务端允许升级协议的响应后,则认为握手完成,后续都以websocket格式发送报文。
websocke报文格式如下:
websocket协议本身很简单,各个字段的含义可以参考其他文章,这里不再详述,本文主要分析tomcat如何实现websocket以及spring boot如何基于tomcat集成websocket。
本文基于tomcat NIO模式进行分析。
分析前可以大致梳理一下,有哪些关键点。
- tomcat NIO模式下,会为每个请求分配一个线程进行处理,websocket是长连接,这个线程是否会与websocket连接绑定,而一直被同一个websocket占有。
- websocket协议升级是在那个点触发的。
- tomcat是如何在http的基础之上支持websocket
- tomcat的websocket是如何暴露接口给spring去集成的。
tomcat NIO模式下线程模型
各个线程的初始化详见org.apache.tomcat.util.net.NioEndpoint#startInternal
- Acceptor线程主逻辑用于调用accept方法接受请求,生成socket并在Poller中注册
- Poller线程主逻辑是调用select方法,获取可读写的socket,并创建SocketProcessor,并丢给Processor线程进行处理。
- Processor线程主要逻辑是根据协议解析socket中的数据,并调用servlet容器进行业务处理。
tomcat NIO模式下请求处理流程
本文不详细解析tomcat的内部实现,http协议本身很简单,没有握手的过程,仅仅只是简单的请求/应答。理论上,只需要 接受请求 -> 解析报文 -> 丢给servlet处理 这几个过程。
tomcat为了实现更丰富的功能抽象出Engine,Host,Context等概念,为了便于理解我们简化一下tomcat模型,我们将CoyoteAdapter到StandardWrapper当成servlet容器内部的行为,将其合并为Servlet Container,其功能就是将request路由到正确的servlet进行处理。
那么请求的处理过程可以简化成
- SocktProcessor主要做协议解析,将socket中的字节流转换成一帧帧HTTP报文
- servlet container主要请求路由,跟url信息转发到正确的servlet进行处理
- FilterChain这个是servlet规范中的FilterChain,在请求在交给servlet处理前,会经过一系列的Filter进行处理
- servlet这个就不解释了。
websocket协议升级涉及的点
- websocket与http有点区别,websocket是长连接(虽然http也可以配置 keepalive在实现长连接,但是服务端并不保证客户端一直没有发请求的情况下仍然保持连接),正常情况下只有双方其中一端显式关闭连接,才能结束这个socket。所以SocktProcessor要了解到该socket连接是有升级过协议的;不仅如此因为websocket是基于http,共用端口,所以SocktProcessor也要感知到socket连接是升级过协议,这样完成握手后的请求,要由websocket的协议处理器去做协议解析。
- websocket协议本身并没有鉴权等设计,这个需要委托给握手阶段的http报文的处理。为此握手阶段的报文要当成正常的http协议处理,需要走所有已配置的Filter;所以握手阶段的处理一定会走到servlet容器里面,而且spring应该会注册专门的handler去处理升级前的http报文。
- servlet处理模型与websocket不一致,所以框架里面应该会有注册websocket的handler的逻辑。