当前位置: 首页 > 后端技术 > Java

学习Tomcat(三)容器连接器

时间:2023-04-01 18:47:36 Java

Tomcat在底层使用Java标准的SocketServer和Socket来接受和处理请求,但是Socket接收到的数据是网络传输层的TCP或UDP协议的数据,这需要转换为Http或其他应用层协议数据。在Tomcat中,连接器用于管理Socket连接,将Scoket请求解析为Request,并将响应数据封装到Response对象中。新版Tomcat容器使用的连接器是Coyote框架。本文将详细介绍Connector的Coyote框架原理。本文很多内容参考了这篇博客什么是CoyoteCoyote是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服务器(如ApacheHTTPServer)集成,优化静态资源和集群部署,目前支持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:使用JavaNIO类库实现。NIO2:使用JDK7最新的NIO2类库实现。APR:使用APR(ApachePortableRuntimeLibrary)实现。8.0之前,Tomcat默认使用的I/O方式是BIO,之后才使用NIO。无论NIO、NIO2还是APR,在性能上都优于之前的BIO。如果使用APR,甚至可以达到接近ApacheHTTPServer的响应性能。在Coyote中,HTTP/2.0的处理方式不同于HTTP/1.1和AJP,是通过升级协议来实现的,这也是由HTTP/2.0的传输方案决定的。可以用一个简单的分层视图来描述Tomcat对协议和I/O方式的支持,如下图所示:接口是具体的Socker接收处理类,是对传输层的抽象。Tomcat没有Endpoint接口,但是提供了一个抽象类AvstractEndpoint。根据不同的I/O方式,提供了NioEndpoint(NIO)、AprEndpoint(APR)和Nio2Endpoint(NIO2)三种实现。Processor:Coyote协议处理接口,负责构造Request和Response对象,通过Adapter提交给Catalina容器处理,是应用层的抽象。Processor是单线程的,Tomcat在同一个连接中复用Processor。Tomcat根据不同的协议提供了三种实现类:Http11Processor(HTTP/1.1)、AjpProcessor(AJP)、StreamProcessor(HTTP/2.0)。此外,他还提供了两个处理内部处理的实现:UpgradeProcessorInternal和UpgradeProcessorExternal,前者用于处理内部支持的升级协议(如HTTP/2.0和WebSocket),后者用于处理外部扩展的升级协议支持。UpgradeProtocol:Tomcat使用UpgradeProtocol接口来表示HTTP升级协议。目前只提供一种实现(Http2Protocol)来处理HTTP/2.0。他根据请求创建一个升级处理的tokenUpgradeToken,其中包含了具体的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对象。如果不存在,则尝试根据协商的协议(如HTTP/2.0请求)构造一个Processor。如果没有协商好的协议(比如HTTP/1.1请求),从回收队列中取出一个释放的Processor对象使用。如果恢复队列中没有对象可用,则通过特定的协议创建一个Processor来使用(同时注册到缓存中)。协议升级时,ConnectionHandler会从当前Processor(如果没有,默认为HTTP/2)获取一个UpgradeToken对象,构造一个升级Processor实例(如果是Tomcat支持的协议,则为UpgradeProcessorInternal,否则会被UpgradeProcessorExternal)替换掉当前的一个Processor,并释放当前的Processor进行回收。替换完成后,链路的后续处理将由升级Process完成。它由UpgradeToken中的HttpUpgradehandler对象的init()方法初始化,以便准备开始启用新协议。我是狐神,欢迎大家关注我的微信公众号:wzm2zsd本文首发于微信公众号,版权所有,禁止转载!