最近有个Web 项目需要了解Tomcat,昨天下载了版本Tomcat9.0.35,
编译了一个Dynamic Web Project,查看console 中的日志信息。分享如下:
结合后附的Tomcat的架构图和处理HTTP 报文的过程图,清楚了Tomcat 服务器里的一个service中的负责HTTP报文连接处理的connector(coyote的AbstractProtocol )、负责HTTP 报文内容处理的container (Engine、host、Context、Wrapper、Servlet)的启动过程和工作原理。
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: Server.服务器版本: Apache Tomcat/9.0.35
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 服务器构建: May 5 2020 20:36:20 UTC
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 服务器版本号(: 9.0.35.0
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 操作系统名称: Windows 10
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: OS.版本: 10.0
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 架构: amd64
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: Java 环境变量: C:\Program Files\Java\jre1.8.0_221
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: Java虚拟机版本: 1.8.0_221-b11
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: JVM.供应商: Oracle Corporation
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: CATALINA_BASE: E:\JavaEEProject\.metadata\.plugins\org.eclipse.wst.server.core\tmp0
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: CATALINA_HOME: E:\Tomcat 9.0
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:-Dcatalina.base=E:\JavaEEProject\.metadata\.plugins\org.eclipse.wst.server.core\tmp0
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:-Dcatalina.home=E:\Tomcat 9.0
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:-Dwtp.deploy=E:\JavaEEProject\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:-Djava.endorsed.dirs=E:\Tomcat 9.0\endorsed
五月 23, 2020 6:51:29 上午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:-Dfile.encoding=GBK
五月 23, 2020 6:51:29 上午 org.apache.catalina.core.AprLifecycleListener lifecycleEvent
信息: 在java.library.path:[C:\Program Files\Java\jre1.8.0_221\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:/Program Files/Java/jre1.8.0_221/bin/server;C:/Program Files/Java/jre1.8.0_221/bin;C:/Program Files/Java/jre1.8.0_221/lib/amd64;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Users\Lenovo\Anaconda3\envs\Deeplearning\Lib\site-packages\;D:\Java\jdk1.8.0_221\bin;D:\Java\jdk1.8.0_221\jre\bin;%GRADLE_HOME%\bin;"D:\Android Studio;D:\Android Studio\tools;D:\Android Studio\build-tools;D:\Android Studio\platform-tools;";D:\zjfw\app\gradle\gradle-5.6-all\gradle-5.6\bin;%CATALINA_HOME%\bin;C:\Users\Lenovo\AppData\Local\Microsoft\WindowsApps;C:\Program Files\JetBrains\PyCharm 2019.1.3\bin;;C:\Users\Lenovo\Anaconda3;C:\Users\Lenovo\Anaconda3\Scripts;C:\Users\Lenovo\Anaconda3\Library\bin;;C:\Users\Lenovo\Desktop;;.]上找不到基于APR的Apache Tomcat本机库,该库允许在生产环境中获得最佳性能
五月 23, 2020 6:51:29 上午 org.apache.coyote.AbstractProtocol init
信息: 初始化协议处理器 ["http-nio-9090"]
五月 23, 2020 6:51:30 上午 org.apache.catalina.startup.Catalina load
信息: 服务器在[1,289]毫秒内初始化
五月 23, 2020 6:51:30 上午 org.apache.catalina.core.StandardService startInternal
信息: 正在启动服务[Catalina]
五月 23, 2020 6:51:30 上午 org.apache.catalina.core.StandardEngine startInternal
信息: 正在启动 Servlet 引擎:[Apache Tomcat/9.0.35]
五月 23, 2020 6:51:30 上午 org.apache.coyote.AbstractProtocol start
信息: 开始协议处理句柄["http-nio-9090"]
五月 23, 2020 6:51:30 上午 org.apache.catalina.startup.Catalina start
信息: [290]毫秒后服务器启动
附件:Tomcat 架构介绍
Tomcat 服务器是一个开源的轻量级Web应用服务器,在中小型系统和并发量小的场合下被普遍使用,是开发和调试Servlet、JSP 程序的首选。
原理
Tomcat结构图:
Tomcat主要组件:服务器Server,服务Service,连接器Connector、容器Container。连接器Connector和容器Container是Tomcat的核心。
一个Container容器和一个或多个Connector组合在一起,加上其他一些支持的组件共同组成一个Service服务,有了Service服务便可以对外提供能力了,但是Service服务的生存需要一个环境,这个环境便是Server,Server组件为Service服务的正常使用提供了生存环境,Server组件可以同时管理一个或多个Service服务。
两大组件
1)Connector
一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。
Tomcat中有两个经典的Connector,一个直接侦听来自Browser的HTTP请求,另外一个来自其他的WebServer请求。HTTP/1.1 Connector在端口8080处侦听来自客户Browser的HTTP请求,AJP/1.3 Connector在端口8009处侦听其他Web Server(其他的HTTP服务器)的Servlet/JSP请求。
Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。
2)Container
Container是容器的父接口,该容器的设计用的是典型的责任链的设计模式,它由四个自容器组件构成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。通常一个Servlet class对应一个Wrapper,如果有多个Servlet定义多个Wrapper,如果有多个Wrapper就要定义一个更高的Container,如Context。
Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。
Engine 容器
Engine 容器比较简单,它只定义了一些基本的关联关系
- Host 容器
Host 是 Engine 的子容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
- Context 容器
Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如何才能找到正确的 Servlet 来执行它呢? Tomcat5 以前是通过一个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了 request 中,在前面的时序图中就可以发现获取子容器都是通过 request 来分配的。
- Wrapper 容器
Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。
Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道。
其他组件
Tomcat 还有其它重要的组件,如安全组件 security、logger 日志组件、session、mbeans、naming 等其它组件。这些组件共同为 Connector 和 Container 提供必要的服务。
Tomcat Server处理一个HTTP请求的过程
1、用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得。
2、Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。
3、Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
4、Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为“ ”的Context去处理)。
5、path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类。
6、构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序。
7、Context把执行完之后的HttpServletResponse对象返回给Host。
8、Host把HttpServletResponse对象返回给Engine。
9、Engine把HttpServletResponse对象返回Connector。
10、Connector把HttpServletResponse对象返回给客户Browser。
Tomcat Server的结构图
配置文件$CATALINA_HOME/conf/server.xml的说明
该文件描述了如何启动Tomcat Server
<!----------------------------------------------------------------------------------------------->
<!-- 启动Server 在端口8005处等待关闭命令 如果接受到"SHUTDOWN"字符串则关闭服务器 -->
<Server port="8005" shutdown="SHUTDOWN" debug="0">
<!-- Listener ??? 目前没有看到这里 -->
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" debug="0"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" debug="0"/>
<!-- Global JNDI resources ??? 目前没有看到这里,先略去 -->
<GlobalNamingResources>
... ... ... ...
</GlobalNamingResources>
<!-- Tomcat的Standalone Service Service是一组Connector的集合 它们共用一个Engine来处理所有Connector收到的请求 -->
<Service name="Tomcat-Standalone">
<!-- Coyote HTTP/1.1 Connector className : 该Connector的实现类是org.apache.coyote.tomcat4.CoyoteConnector port :
在端口号8080处侦听来自客户browser的HTTP1.1请求 minProcessors : 该Connector先创建5个线程等待客户请求,
每个请求由一个线程负责 maxProcessors : 当现有的线程不够服务客户请求时,若线程总数不足75个,则创建新线程来处理请求
acceptCount : 当现有线程已经达到最大数75时,为客户请求排队 当队列中请求数超过100时,后来的请求返回Connection refused
错误 redirectport : 当客户请求是https时,把该请求转发到端口8443去 其它属性略 -->
<Connector className="org.apache.coyote.tomcat4.CoyoteConnector"
port="8080"
minProcessors="5" maxProcessors="75" acceptCount="100"
enableLookups="true"
redirectPort="8443"
debug="0"
connectionTimeout="20000"
useURIValidationHack="false"
disableUploadTimeout="true" />
<!-- Engine用来处理Connector收到的Http请求 它将匹配请求和自己的虚拟主机,
并把请求转交给对应的Host来处理默认虚拟主机是localhost -->
<Engine name="Standalone" defaultHost="localhost" debug="0">
<!-- 日志类,目前没有看到,略去先 -->
<Logger className="org.apache.catalina.logger.FileLogger" .../>
<!-- Realm,目前没有看到,略去先 -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" .../>
<!-- 虚拟主机localhost appBase : 该虚拟主机的根目录是webapps/ 它将匹配请求和
自己的Context的路径,并把请求转交给对应的Context来处理 -->
<Host name="localhost" debug="0" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- 日志类,目前没有看到,略去先 -->
<Logger className="org.apache.catalina.logger.FileLogger" .../>
<!-- Context,对应于一个Web App path : 该Context的路径名是"",故该Context是该Host的默认Context docBase : 该Context的根目录是webapps/mycontext/ -->
<Context path="" docBase="mycontext" debug="0"/>
<!-- 另外一个Context,路径名是/wsota -->
<Context path="/wsota" docBase="wsotaProject" debug="0"/>
</Host>
</Engine>
</Service>
</Server>
<!----------------------------------------------------------------------------------------------->
Context的部署配置文件web.xml的说明
一个Context对应于一个Web App,每个Web App是由一个或者多个servlet组成的。
当一个Web App被初始化的时候,它将用自己的ClassLoader对象载入“部署配置文件web.xml”中定义的每个servlet类。
它首先载入在$CATALINA_HOME/conf/web.xml中部署的servlet类,
然后载入在自己的Web App根目录下的WEB-INF/web.xml中部署的servlet类,
web.xml文件有两部分:servlet类定义和servlet映射定义。
每个被载入的servlet类都有一个名字,且被填入该Context的映射表(mapping table)中,和某种URL PATTERN对应;
当该Context获得请求时,将查询mapping table,找到被请求的servlet,并执行以获得请求回应;
分析一下所有的Context共享的web.xml文件,在其中定义的servlet被所有的Web App载入;
<!----------------------------------------------------------------------------------------------->
<web-app>
<!-- 概述: 该文件是所有的WEB APP共用的部署配置文件, 每当一个WEB APP
被DEPLOY,该文件都将先被处理,然后才是WEB APP自己的/WEB-INF/web.xml -->
<!-- +-------------------------+ -->
<!-- | servlet类定义部分 | -->
<!-- +-------------------------+ -->
<!-- DefaultServlet
当用户的HTTP请求无法匹配任何一个servlet的时候,该servlet被执行
URL PATTERN MAPPING : / -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>
org.apache.catalina.servlets.DefaultServlet
</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- InvokerServlet
处理一个WEB APP中的匿名servlet 当一个servlet被编写并编译放入
/WEB-INF/classes/中,却没有在/WEB-INF/web.xml中定义的时候
该servlet被调用,把匿名servlet映射成/servlet/ClassName的形式
URL PATTERN MAPPING : /servlet/* -->
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>org.apache.catalina.servlets.InvokerServlet </servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- JspServlet
当请求的是一个JSP页面的时候(*.jsp)该servlet被调用
它是一个JSP编译器,将请求的JSP页面编译成为servlet再执行
URL PATTERN MAPPING : *.jsp -->
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>WARNING</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- +---------------------------+ -->
<!-- | servlet映射定义部分 | -->
<!-- +---------------------------+ -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>invoker</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<!-- +------------------------+ -->
<!-- | 其它部分,略去先 | -->
<!-- +------------------------+ -->
... ... ... ...
</web-app>
<!----------------------------------------------------------------------------------------------->
Connector架构图
Connector就是使用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。
其中ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter。
(1)Endpoint用来处理底层Socket的网络连接,Processor用于将Endpoint接收到的Socket封装成Request,Adapter用于将Request交给Container进行具体的处理。
(2)Endpoint由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的,而Processor用来实现HTTP协议的,Adapter将请求适配到Servlet容器进行具体的处理。
(3)Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步Request的超时,Handler用于处理接收到的Socket,在内部调用Processor进行处理。
Container处理的责任链模式
Container处理请求是使用Pipeline-Value管道来处理的!Pipeline-Value是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。
Container包含四个子容器,而这四个子容器对应的BaseValue分别在:StandardEngineValue、StandardHostValue、StandardContextValue、StandardWrapperValue。
Pipeline-Value使用的责任链模式和普通的责任链模式有些不同!区别主要有以下两点:
- 每个Pipeline都有特定的Value,而且是在管道的最后一个执行,这个Value叫做BaseValue,BaseValue是不可删除的;
在上层容器的管道的BaseValue中会调用下层容器的管道。
Pipeline的处理流程图如下
(1)Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline(Engine的管道);
(2)在Engine的管道中依次会执行EngineValue1、EngineValue2等等,最后会执行StandardEngineValue,在StandardEngineValue中会调用Host管道,然后再依次执行Host的HostValue1、HostValue2等,最后在执行StandardHostValue,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValue。
(3)当执行到StandardWrapperValue的时候,会在StandardWrapperValue中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理!
(4)当所有的Pipeline-Value都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端。
详细信息参考链接:四张图带你了解Tomcat系统架构
https://blog.csdn.net/qq_38245537/article/details/79009448