一、前言
前面我们重点讨论了容器组件的设计,集中于如何设计才能确保容器的灵活性和可扩展性,并且做到合理的解耦。接下来,我们再细化一下服务器设计中的另外一个重要组件——Connector。
二、Connector
要想与Container配合实现一个完整的服务器功能,Connector至少要完成一下几项功能:
- 监听服务器端口,读取来自客户端的请求。
- 将请求数据按照指定协议进行解析。
- 根据请求地址匹配正确的容器进行处理。
- 将响应返回客户端。
只有这样才能保证接收到的客户单请求交由与请求地址匹配的容器处理。
我们知道,Tomcat支持多协议,默认支持HTTP和AJP。同时,Tomcat还支持多种 I/O 方式,包括BIO、NIO、APR。而且在Tomcat 8 之后新增了对NIO2和HTTP/2协议的支持。因此,对协议和 I/O 进行抽象和建模是需要重点关注的。
Tomcat的设计方案如下图所示:
在Tomcat中,ProtocolHandler表示一个协议处理器,针对不同的协议和 I/O 方式,提供了不同的实现,如HTTP11NioProtocol表示基于NIO的HTPP协议处理器。ProtocolHandler包含一个Endpoint用于启动Socket监听,该接口按照 I/O方式进行分类实现,如Nio2Endpoint表示非阻塞式Socket I/O。还包含了一个Processor用于按照指定协议读取数据,并且将请求交由容器处理,如Http11NioProcessor表示在NIO的方式下HTPP请求的处理类。
在Connector启动时,Endpoint会启动线程来监听服务器端口,并且在接收到请求后调用Processor进行数据读取。
当Processor读取客户端请求后,需要按照请求地址映射到具体的容器进行处理,这个过程即为请求映射。由于Tomcat各个组件采用通用的生命周期进行管理,并且可以通过管理工具进行状态变更,因此请求映射除烤炉映射规则的实现外,还要考虑容器组件的注册于注销。
Tomcat通过Mapper和MapperListener两个雷实现上述功能。前者用于维护容器映射信息,同时按照映射规则(Servlet规范定义)查找容器。后者实现了ContainerListener和LifecycleListener,用于在容器组件状态发生变更时,注册或者取消对应的容器映射信息。为了实现上述功能,MapperListener实现了Lifecycle接口,当其启动时(Service启动时启动),会自动作为监听器注册到各个容器组件上,同时将已创建的容器注册到Mapper。
Tomcat通过适配器模式(Adapter)实现了Connector与Mapper、Container的解耦。Tomcat默认的Connector实现(Coyote)对应的适配器为CoyoteAdapter。也就是说,如果你希望使用Tomcat的链接器方案,但是又想脱离Servley容器,此事只需要实现我们自己的Adapter即可。当然,我们好需要按照Container的定义开发我们自己的容器(不一定遵循Servlet规范)。
按照上述描述,Connector的设计图如下所示:
三、Executor
完成了Connector的设计之后,我们再进一步审视一下当前的应用服务方案,很明显,忽略了一个问题——并发。这对应用服务器而言是尤其需要考虑的,我们不可能让所有来自客户端的请求以串行的方式执行。那么我们又该如何设计应用服务器的并发方案?
首先,Tomcat提供了Executor接口来表示一个可以再组件间共享的线程池(默认使用JDK5提供的线程池技术),该接口同样继承自Lifecycle,可以按照通用的组件进行管理。
其次,线程池的共享范围如何确定?在Tomcat中Executor又Service维护,因此同一个Service中的组件可以共享一个线程池。
当然,如果没有定义任何线程池,相关组件(如Endpoint)会自动创建线程池,此时,线程池不在共享。
在Tomcat中,Endpoint会启动一组线程池来监听Socket端口,当接收到客户端请求后,会创建请求处理对象,并且交由线程池处理,由此并发处理客户端的请求。
添加Executor后,总体设计如下图所示:
四、Tomcat组件说明
组件名称 | 说明 |
---|---|
Server | 表示整个Servlet容器,由此Tomcat运行环境中只有唯一一个Server实例 |
Service | Service表示一个或者多个Connector的集合,这些Connector共享同一个Container来处理其请求。在同一个Tomcat实例内可以包含任意多个Service实例,他们彼此成立 |
Connector | 即Tomcat链接器,用于监听并且转化Socker请求,同时将读取的Socker请求交由COntainer处理,支持不同协议以及不同的 I/O方式 |
Container | Container表示能够执行客户端请求并且返回响应的一类对象。在Toncat中存在不同级别的容器:Engine、Host、Context、Wrapper |
Engine | Engine表示整个Servlet引擎。在Tomcat中,Engine为最高层级的容器对象。尽管Engine不是直接处理请求的容器,确实获取目标容器的入口 |
Host | Host作为一类容器,表示Servlet引擎(即Engine)中的虚拟机,与一个服务器的网络名称有关,如域名等。客户端可以使用这个网络名称连接服务器,这个名称必须要在DNS服务器上注册 |
Context | Context作为一类容器,用于表示ServletContext,在Servlet规范中,一个ServletContext即表示一个独立的Web应用 |
Wrapper | Wrapper作为一类容器,用于表示Web应用中定义的Servlet |
Executor | 表示Tomcat组件间可以共享的线程池 |