-
学习Tomcat(三)之容器连接器
Tomcat最底层使用的是Java标准的SocketServer和Socket接受和处理请求,但是Socket接受到的数据是网络运输层的TCP或UDP协议的数据,需要转为Http或者其它应用层协议的数据。Tomcat中就是通过连接器Connector来管理Socket连接、解析Scoket请求为Request并封装响应数据为Response对象。新版本的Tomcat容器使用的连接器是Coyote框架,本文会详细介绍Connector的Coyote框架原理。本文很多内容参考了这篇博客
什么是Coyote
Coyote是Tomcat链接器框架的名称,是Tomcat服务器提供的供客户端访问的外部接口。客户端通过Coyote与服务器建立链接、发送请求并且接收响应。
Coyote封装了底层的网络通信(Socket请求以及响应处理),为Catalina容器提供了统一的接口,是Catalina容器与具体的请求协议以及 I/O方式解耦。Coyote将Socket输入转换为Request对象,交由Catalina容器进行处理,处理请求完成之后,Catalina通过Coyote提供的Response对象将结果写入输出流。
Coyote作为独立的模块,只负责具体协议和I/O的处理,与Servlet规范实现没有直接关系,因此即便是Request和Response对象也并未实现Servlet规范对应的接口,而是在Catalina中将他们进一步封装为ServletRequest何ServletResponse。
Coyote支持的协议
Coyote框架支持以下三种应用层协议:
- HTTP/1.1协议:这是绝大多数Web应用采用的访问协议,主要用于Tomcat单独运行(不予Web服务器集成)的情况。
- AJP协议:用于和Web服务器(如Apache HTTP Server)集成,以实现针对静态资源的优化以及集群部署,当前支持AJP/1.3。
- HTTP/2.0协议:下一代HTTP协议,自Tomcat8.5以及9.0版本开始支持,截止目前,主流的最新版本均已支持HTTP/2.0。
针对HTTP和AJP协议,Coyote又按照 I/O方式分别提供了不同的选择方案(自8.5/9.0版本起,Tomcat已出了对BIO的支持)。
- NIO:采用Java NIO类库实现。
- NIO2:采用JDK 7最新的NIO2类库实现。
- APR:采用APR(Apache可移植运行库)实现
在8.0之前,Tomcat默认采用的I/O方式为BIO,之后采用NIO。无论NIO、NIO2还是APR,在性能方面均优于以往的BIO。如果采用APR,甚至可以达到接近于 Apache HTTP Server的响应性能。
在Coyote中,HTTP/2.0的处理方式与HTTP/1.1和AJP不同,采用一种升级协议的方式实现,这也是有HTTP/2.0的传输方案决定的。
可以采用一种简单的分层视图来描述Tomcat对协议以及 I/O方式的支持,如下图所示:
Connector的核心概念
在Connector中有如下几个核心概念:
- Endpoint:Coyote通信端点,即通信监听的接口,是具体的Socker接收处理类,是对传输层的抽象。Tomcat并没有Endpoint接口,而是提供了一个抽象类AvstractEndpoint。根据I/O方式的不同,提供了NioEndpoint(NIO)、AprEndpoint(APR)以及Nio2Endpoint(NIO2)三个实现。
- Processor:Coyote协议处理接口,负责构造Request和Response对象,并且通过Adapter将其提交到Catalina容器处理,是对应用层的抽象。Processor是单线程的,Tomcat在同一次链接中复用Processor。Tomcat按照协议的不同提供了3个实现类:Http11Processor(HTTP/1.1)、AjpProcessor(AJP)、StreamProcessor(HTTP/2.0)。除此之外,他还提供了两个用于处理内部处理的实现:UpgradeProcessorInternal和UpgradeProcessorExternal,前者用于处理内部支持的升级协议(如HTTP/2.0和WebSocket),后者用于处理外部扩展的升级协议支持。
- UpgradeProtocol:Tomcat采用UpgradeProtocol接口表示HTTP升级协议,当前只提供了一个实现(Http2Protocol)用于处理HTTP/2.0.他根据请求创建一个用于升级处理的令牌UpgradeToken,该令牌中包含了具体的HTTP升级处理器HttpUpgradeHandler,HTTP/2.0的处理器实现为Http2UpgradeHandler。Tomcat中的WebSocket也是通过UpgradeToken机制实现的。
Connector请求处理流程
Connector处理请求的流程如上所示,我们一步步分析一下流程的内容:
- 当Connector启动时,会同时启动其持有的Endpoint实例。Endpoint并允许多个线程(有属性acceptorThreadCount确定),每个线程允许一个AbstractEndpoint.Acceptor实例。在AbstractEndpoint.Acceptor实例中监听端口通信(I/O方式不同,具体的处理方式也不同),而且只要Endpoint处于运行状态,始终循环监听。
- 当监听到请求时,Acceptor将Socker封装为SocketWrapper实例(此时并未读取数据),并交由一个SocketProcessor对象处理(此过程也由线程池异步处理)。此部分根据I/O方式的不同处理会有所不同,如NIO采用轮询的方式检测SelectionKey是否就绪。如果就绪,则获取一个有效的SocketProcessor对象并且提交线程池处理。
- SocketProcessor是一个线程池Worker实例,每一个I/O方式均有自己的实现。他首先判断Socket的状态(如完成SSL握手),然后提交到ConnectionHandler处理。
-
ConnectionHandler是AbstractProtocol的一个内部类,主要用于链接选择一个合适的Processor实现以进行请求处理。
- 提升性能,他针对每个有效的理解都会缓存器Processor对象。不仅如此,当前链接关闭时,其Processor对象还会被释放到一个回收队列(升级协议不会回收),这样后续链接可以重置并且重复利用,以减少对象构造。
- 在处理请求时,他首先会从缓存中获取当前链接的Processor对象。如果不存在,则尝试根据协商协议构造Processor(如HTTP/2.0请求)。如果不存在协商协议(如HTTP/1.1请求),则从回收队列中获取一个已释放的Processor对象使用。如果回收队列中没有可用的对象,那么由具体的协议创建一个Processor使用(同时注册到缓存)。
- 协议升级时,ConnectionHandler会从当前Processor得到一个UpgradeToken对象(如果没有,则默认为HTTP/2),并构造一个升级Processor实例(如果是Tomcat支持的协议则会是UpgradeProcessorInternal,否则是UpgradeProcessorExternal)替换当前的Processor,并将当前的Processor释放回收。替换后,该链接的后续处理将由升级Process完成。
- 通过UpgradeToken中HttpUpgradehandler对象的init()方法进行初始化,以便准备开始启用新协议。