Featured image of post 一步步带你了解Tomcat中的连接器是如何设计的

一步步带你了解Tomcat中的连接器是如何设计的

一、Tomcat的整体架构设计

Tomcat有2个核心功能:1、处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。2、加载和管理 Servlet,以及具体处理 Request 请求。因此 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。

tomcat-struct-1.jpg-msr

Tomcat 支持的 I/O 模型有:

  • NIO:非阻塞 I/O,采用 Java NIO 类库实现。
  • NIO2:异步 I/O,采用 JDK 7 最新的 NIO2 类库实现。
  • APR:采用 Apache 可移植运行库实现,是 C/C++ 编写的本地库。

Tomcat 支持的应用层协议有:

  • HTTP/1.1:这是大部分 Web 应用采用的访问协议。
  • AJP:用于和 Web 服务器集成(如 Apache)。
  • HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。

Tomcat 为了实现支持多种 I/O 模型和应用层协议,一个容器可能对接多个连接器,就好比一个房间有多个门。但是单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作 Service 组件。这里请你注意,Service 只是在连接器和容器外面多包了一层,把它们组装在一起。Tomcat 内可能有多个 Service,这样的设计也是出于灵活性的考虑。通过在 Tomcat 中配置多个 Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

二、Tomcat连接器

连接器屏蔽了各种协议和I/O模型的区别,最后对于Servlet容器来说都能获取到一个标准的ServletRequest对象。对于连接器的工作流程大致如下:

  • 监听网络端口,等待连接请求
  • 接受网络连接请求
  • 读取请求中的字节流
  • 根据具体应用层协议(HTTP/APR)解析字节流,生成统一的Tomcat Request对象
  • 将Tomcat Request对象转换成标准的ServletRequest提交给Servlet容器
  • 调用Servlet容器,得到ServletResponse
  • 将ServletResponse转换成Tomcat Response对象
  • 将Tomcat Response转成网络字节流
  • 将响应字节流写回浏览器

在上面的连接器的工作流程可以得出,连接器有三个主要的功能:网络通信、应用层协议解析、Tomcat Request/Response与ServletRequest/ServletResponse之间的转换。所以Tomcat的设计者设计了3个组件来实现这三个功能:Endpoint、Processor和Adapter。

Endpoint负责提供字节流给Processor处理,Processor处理好之后提供Tomcat Request对象给Adapter,Adapter将Tomcat Request对象转成标准的ServletRequest提供给Servlet容器。

I/O模型和应用层协议可以自由组合,例如:NIO+HTP、NIO2+HTTP或者NIO2+AJP。所以在Tomcat的设计中,有一个ProtocolHandler的接口,来处理I/O和应用层协议的变化。各种通讯模型和协议的组合都有对应的具体的实现类,例如Http11NioProtocol和AjpNioProtocol。

在上面的图可以看到在Tomcat中每一种I/O和应用层协议的组合都有具体的实现类。在使用的使用可以自由组合。

2.1 连接器三大组件的关系

连接器三大核心组件:Endpoint、Processor和Adapter上面已经介绍了它们的作用,其中在上面的类继承图中可以看到Endpoint+Processor放在一起被抽象成ProtocolHandler组件,如下:

2.2 ProtocolHandler组件

上面的类继承图中可以知道Tomcat中连接器使用ProtocolHandler来处理网络连接和应用层协议,主要包含Endpoint和Processor。

1、Endpoint

Endpoint是通信端点,是具体的Socket接收和发送处理器,是对传输层的抽象,所以Endpoint是用来实现TPC/IP协议的。

AbstractEndpoint是Endpoint对应的抽象接口,具体的实现类有NioEndpoint和Nio2Endpoint,有两个重要的子组件Acceptor和SocketProcessor。

Acceptor是用来监听Socket连接请求。SocketProcessor用来处理接收到的Socket请求,它实现了Runnable接口,在run方法中调用协议处理组件Processor进行处理。Tomcat为了提高处理能力。SocketProcessor被提交到线程池中执行。这个线程池在Tomcat中被命名为执行器(Executor)。

2、Processor

Endpoint是用来实现TCP/IP协议,Processor是用来实现HTTP协议,Processor接受来自Endpoint的Socket,解析成Tomcat Request和Response对象,并通过Adapter提交到容器中,在上图中可见到。Processor是对应应用层协议的抽象。其类图如下,Processor是个接口,定义了请求的处理等方法。它的抽象实现类 AbstractProcessor 对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有 AJPProcessor、HTTP11Processor 等,这些具体实现类实现了特定协议的解析方法和请求处理方式。

所以连接器的组件又进一步细化成如下的结构图,EndPoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 Run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用Adapter 的 Service 方法。

2.3 适配器Adapter

发送过来的请求可能协议会不同,客户端发过来的请求信息也不尽相同,Tomcat 定义了自己的 Request 类来“存放”这些请求信息。ProtocolHandler 接口负责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest,也就意味着,不能用 Tomcat Request 作为参数来调用容器。Tomcat 设计者的解决方案是引入 CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 Service 方法。

三、总结

Tomcat 的整体架构包含了两个核心组件连接器和容器。连接器负责对外交流,容器负责内部处理。连接器用 ProtocolHandler 接口来封装通信协议和 I/O 模型的差异,ProtocolHandler 内部又分为 EndPoint 和 Processor 模块,EndPoint 负责底层 Socket 通信,Proccesor 负责应用层协议解析。连接器通过适配器 Adapter 调用容器。

Licensed under CC BY-NC-SA 4.0