因此Tomcat设计了两个核心组件连接器(Connetor)和容器(Container)来分别做这两件事情。连接器负责处理对外交流,容器负责处理内部逻辑。
Tomcat为了实现多种I/O模型和应用层协议,一个容器可能对多个连接器。但单独的连接器或容器不能对外提供服务,需要把他们组装起来才能工作,组装后后的这个整体叫做Service组件。Tomcat内可能有多个Service,配置多个Service,可以实现不同端口来访问同一台机器上部署的不同应用。
从上图可以看到,最顶层是Server,这里的Server指的是一个Tomcat实例。一个Server中有一个或多个Service,一个Service中有多个连接器和一个容器。连接器与容器之间通过标准的ServletRequest和ServletResponse通信。
连接器替Servlet容器屏蔽了协议及I/O模型的区别,无论是HTTP还是AJP,在容器中获取到的都是一个标准的ServletRequest对象。
1)监听网络端口。
2)接受网络连接请求。
3)读取网络请求字节流。
4)根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的Tomcat Request 对象。
5)将Tomcat Request对象转成标准的ServletRequest。
6)调用Servlet容器,得到ServletResponse。
7)将ServletResponse转成Tomcat Response对象。
8)将Tomcat Response转换成网络字节流。
9)将响应字节流写回给浏览器。
连机器模块有三大核心组件:Endpoint、Processor和Adapter 来分别做三件事情:网络通信、应用层网络协议解析、TomcatRequest/TomcatResponse 与ServletRequest/ServletResponse的转化。
整体正向流程:Endpoint负责提供字节流给Processor,Processor负责提供Tomcat Request 对象给Adapter,Adapter负责提供ServletRequest对象给容器。
从上图可以看出连接器用ProtocolHandler来处理网络连接和应用层协议,包含了两个重要组件: Endpoint和Processor。
Endpoint 是通信端点,即通信监听的接口,是具体的Socket接收和发送处理器,是对传输层的抽象,因此Endpoint是用来实现TCP/IP协议的。
Endpoint 是一个接口,对用的抽象实现类是AbstractEndpoint,而AbstractEndpoint的具体子类比如:NioEndpoint和Nio2Endpoint中,有两个重要的子组件:Acceptor 和SocketProcessor。
Acceptor 用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现了Runable接口,在run方法里调用协议处理组件Processor进行处理。
Processor 是一个接口,定义了请求的处理等方法。它的抽象实现类AbstractProcessor对一些协议公用的属性进行封装,没有对方付进行实现。具体的实现类有AjpProcessor、Http11Processor等,这些具体的实现类实现了特定协议的解析方法和请求处理方式。
连接器组件图:
从图中可以看出,Endpoint收到Socket连接后,生成一个SocketProcessor任务提交到线程池处理,SocketProcessor的run方法会调用Processor组件去解析应用层协议,Processor通过解析生成Tomcat Request对象后,会调用Adapter的Service方法。
Tomcat 设计了4中容器,分别是Engine、Host、Context和Wrapper。这四种容器是父子广西如下图:
可以通过Tomcat的server.xml配置文件加深对Tomcat容器的理解。
上图可以发现容器具有父子关系,是一个树形结构,这是典型设计模式的组合模式。所有的容器都实现了Container接口,因此组合模式尅使得用户对单容器对象和组合容器对象使用具有一致性。Wrapper单容器对象,其它的是组合容器对象。
加入有一个购物网站,有面向终端用户的购物系统,也有面向网站管理员的后台管理系统。这两个系统都跑在同一个Tomcat上,为了隔离它们的访问域名,配置了两个虚拟域名:man.age.shopping.com和user.shopping.com,网站管理员通过man.age.shopping.com访问Tomcat去管理用户和商品,而用户管理和商品管理是两个独立的Web应用。终端用户通过man.age.shopping.com去搜索商品和下订单,搜索商品 和订单管理也是两个独立的Web应用。
针对这种部署,Tomcat 会创建一个Service组件和一个Engine容器组件,在Engine容器下创建两个Host子容器,在每个Host容器下创建两个Context子容器。由于一个Web应用通常有多个Servlet,Tomcat还会在每个Context容器里创建多个Wrapper子容器,每个容器都有对应的访问路径。
1)根据协议和端口号选定Service 和Engine
我们知道Tomcat的每个连接器都监听不同的端口,比如Tomcat默认的HTTP连机器箭筒8080端口。上面例子中URL访问的是8080端口,因此这个请求会被HTTP连接器接收。一个Service组件有多个连接器,还有一个容器(Engine)组件,因此Service确定了也就意味着Engine也确定了。
2)根据域名选中host
Service 和Engine确定后,Mapper组件通过URL中的域名查找相应的Host容器,比如:例子中的URL访问域名是user.shopping.com,因此Mapper会找到Host2这个容器。
3)根据URL路径选择Context组件
Host确定以后,Mapper根据URL的路径来匹配相应的Web应用路径,比如访问中的/order,因此找到了Context4这个Context容器。
4)根据URL路径找到Wrapper(Servlet)
Context确定后,Mapper再根据web.xml中配置的Servlet映射路径来找到具体的Wrapper和Servlet。
容器中执行流程:连接器的Adapter调用容器的Service方法来执行Servlet,最先请求到的是Engine容器,然后请求传给其子容器Host,Host请求传给其子容器Context,最后请求Wrapper容器,Wrapper会调用最终的Servlet来处理。 这个调用过程是通过PipeLine-Valve管道。
PipeLine-Valve管道是责任链模式,一个请求处理过程中有多个处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完后之后调用下一个处理者处理。
Wrapper容器最后一个Valve会蒋健一个Filter链,并调用doFilter方法,最终会调到Servlet的Service方法。