《tomcat简单介绍》


    一、背景介绍

            早期的web应用只是浏览一些简单的新闻静态页面,然后有浏览器解析html,返回给用户,随着互联网的发展,用户要求越来越高,不仅仅只是看一下静态页面,更多希望有一些交互,显示动态效果,并且可让http服务器可以调用服务端程序,这时候sun公司开发大牛们研究出了一套servlet技术,可以把它看成运行在服务端的小程序,但是servlet没有main方法,于是这是就衍生了一个容器,可以运行servlet,这就是servlet容器,tomcat就是一个很好servlet容器;

   二、概念

         tomcat 简单来说,就是一个“http服务器+servlet容器”的一个web容器;

   三、tomcat如何执行请求

        http:  浏览器与服务端传送协议,底层是TCP/IP协议,浏览器发送请求,通过http协议封装报文发送给服务端, TCP/IP协议的三次握手;

        上面说了tomcat是”http服务器+servlet容器“,当客户端浏览器发送一个http协议的请求,tomcat会通过自生创建request对象来接受请求,并且解析,获取调用的servlet,这时候如果servlet如何初始化了,就是执行doGet或者doPost方法执行响应的业务逻辑,如果没有初始化,就调用init方法进行初始化,执行完响应的业务逻辑后,tomcat会再次通过自生创建的response对象来接受结果,并返回给客户端;

    四、tomcat的server.xml

       介绍tomcat不能不介绍的配置文件 \apache-tomcat-8.5.61\conf\server.xml,这里存的tomcat核心组件,tomcat就是组件组成的容器;

<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!--APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>

<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">

<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443
This connector uses the NIO implementation. The default
SSLImplementation will depend on the presence of the APR/native
library and the useOpenSSL attribute of the
AprLifecycleListener.
Either JSSE or OpenSSL style configuration may be used regardless of
the SSLImplementation selected. JSSE style configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
This connector uses the APR/native implementation which always uses
OpenSSL for TLS.
Either JSSE or OpenSSL style configuration may be used. OpenSSL style
configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
-->

<!-- Define an AJP 1.3 Connector on port 8009 -->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
-->

<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->

<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">

<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->

<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>

<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="false" deployOnStartup = "false">

<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->

<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>
</Server>

    从上面,我们可以得到下面一张组件构造图(下面类就是对应的每个组件在java中实现类)

     

     五、源码解析(tomcat执行流程)     

     作为java开发人员,说那么多废话干嘛,直接上源码剖析(这里执行讲一下主干,至于更多的细节,伙伴们自己研究啊,能力和时间有限,我是自己复习用的,哈哈)       

1、最近比较火的一部电视剧,斗罗大陆大家应该知道,个人更喜欢动漫,因为视觉效果精彩并且故事长,在这个斗罗大陆上所有的魂师必须在很小的时候,进行武魂觉醒才能开始闯荡斗罗世界里,java世界里也一样,所有的入口都是main方法,所以tomcat也是先从main方法开始的,执行main方法后,Bootstrap对象init方法,通过反射创建出了Catalina对象赋值给daemon, 然后我们启动通过catalina.bat(set ACTION=start),传入参数,通过参数start执行if判断块里的代码,然后我们分两部分 load和start,  前者是加载初始化,后者是启动; 我们先说加载初始化组件;

 

2、点击load方法,进入方法体,我们看catalinaDaemon就是Catalina对象,通过反射执行Catalina的load方法;

 

3、进入Catalina对象的load方法,主干两大核心 ,一个创建解析器Digester,然后解析server.xml里所有的组件找到对应的实例,解析的类太多了,只截图了一点,有兴趣可以自己点击进行研究; 然后就是初始化Server,接下来讲的你都可以对应着server.xml里每一个标签(组件)对应,这就是先初始化组件,才能进行工作;

 

 3、接下来点击方法进行后,进入了LIfecycle接口,大家不用惊讶,这就是tomcat优点。tomcat所有的组件的生命周期都是交给了Lifecycle接口,既然接口必有实现类,

 LifecycleBase类就是整个tomcat组件的生命周期同时也是一个抽象类,执行init方法,其实也就是子类initInternal()方法;

   

4、执行initInternal()方法其实就是执行了StandarServer的initInternal()方法,StandarServer对应着server.xml中server标签,在这个类里,重要的就是执行了StandarService的初始化也就是init方法,大家通过server.xml里就能知道,server标签下有多个service标签,所以这里是数组;

  5、和3步骤一样,点击services[i].init()方法,会通过LifecycleBase抽象类,调用子类的initInternal()方法,这里执行就是StandarService的initInternal()方法,这里初始化就很重要的了,从这里面进行初始化的就是tomcat的几大容器,engine,host,context,Wrapper;并且初始化Htttp服务器connector;

那我么就先说容器初始化:容器的初始化也是一样的套路,继承了tomcat的生命周期LifeclcyBase,我就不一一说了,直接看截图;

 进入StandardEngine类后, 没有多余的操作,直接调用super的initInternal()方法,这里他的父类就不是LifeclcyBase了,因为是容器,所以是containerBase类

 点击super.initInternal()方法,这里就启动了线程  starStop线程池,我们知道tomcat的所有工作都是线程来执行的,这个线程也就是host,context,wrapper的启动线程

以上是容器初始化,下面咱们说htttp服务器初始化,回到第5步骤,StandarService类中找到initInternal()方法,找到connector.init(), connector就是http服务器,

对了还有一个方法mapperListener.init()方法,这个方法就是来选择是哪个host,context,wrapper;

 点击进来connector.init方法进入后,一样还是进入了生命周期类LifeclcyBase类中,调用子类执行子类的initInternal()方法;这时候就会找到Connector类中的initInternal()方法,这个方法中protocolHandler在Connector对象的构造方法中执行setProtocol(protocol),当前这个protocol就是server.xml中Connector标签中配置的protocol="HTTP/1.1"  根据参数进入方法最后获取Http11NioProtocol类,然后回到Connector.initInternal()方法中 ProtocolHandler.init()方法其实就是Http11NioProtocol的init的方法;

 

 进入到ProtocolHandler.init()方法执行子类AbstractProtocol.init()方法,最重要一点就是endpoint.init();然后调用AbstractEndpoint类的init方法中bind()方法,调用子类NioEndpoint的bind()方法,绑定端口;

  

==============================以上部分都是tomcat各个组件的初始化========================================================

那么组件初始化完毕后, 我们就开始用了,首先用的话肯定是需要启动,所以我们找到最初的bootstrap类中if判断块中找到daemon.start(),初始化的时候,我们说了daemon就是Catalina类,找到Catalina的start()方法,里面找到server.start()方法,之前我们说过tomcat的生命周期是LifeslsyBase,所以我们通过找子类的方式最终找到StandardService中startInternal()中,我们可以看到同样是engine.start()、mapperLIstener.start()和connector.start()方法,上面说过mapperLIstener.start()就是告诉我们是那个host,context,wrapper,  这个就不多说了,我们来分析engine.start()和connector.start()方法;

  根据生命周期子类执行startInternal()方法我们找到StandardEngine的startInternal()然后执行子类,我们说了从engine就是容器了,所以这里执行的是ContainerBase的starInternal()方法

上面方法我们看到Container Children[] = findChildren()方法,这个方法就是从容器的子容器,我们知道engine下面是host,host下面context,那么我们进入StandardContext类中(注意:new StartChild()方法进来后,发现它implements Callable<Void> 那么就会去执行call()方法,去执行子容器的start());在StandardContext的startInternal()方法中我们看到下面的红框中方法,这个方法是监听,这个监听到我们一开始我们解析了一个类ContextConfig, 那么这里就是解析web.xml中的listener,filter,servlet;

 

点击webConfig()方法就是真正的开始解析!

这块我们就算源码分析完毕了,那么我们接着开始分析http服务器那块的启动,那么我们找到StandardService类中找到connector.start();

进入方法后,我们就直接调用子类的StartInternal()方法

点击protocolHandler.start(),跳转到AbstractProtocol类的start()方法

这里最重要的是endpoint.start()方法,然后还启动了一个超时的线程,然后我们点击endpoint.start()方法跳转到NioEndPoint类中startInternal()方法,因为tomcat底层用的是NIO,所以是NioEndPoint类;在这里个类中有两个重要的类 Acceptor   和 Poller

 

我们先说Acceptor, 这个类就是接受http请求,然后把http请求放到多路复用器Poller中,但其实它只是把http放到了Poller中的一个队列中,看下面的代码

看上面setSocketOptions(socket),点击进去,addEvent(r) 就是放到队列中

 通过取模算法,获取到poller,然后把socket放到poller的队列中

 然后我开始介绍Poller,也就是多路复用器,然后实现了Runnable接口,执行run方法

 从队列中取出请求连接socket

然后执行processkey()

 执行processSocket()

 在上面这个方法中,主要启动了工作线程池,然后执行SocketProcessorBase类

 进入SocketProcessorBase中实现了Runnable,执行run方法,然后执行doRun()方法,执行子类的NioEndpoint的doRun方法

 

 

 

 

 

 

 

 以上代码,就直接点击就可以了,然后到Http11Processor类中service方法中最重要的方法就是一下代码

 将分装好的请求对象和响应对象,交由容器处理;

 这里就通过tomcat 最重要的一点就是通过容器的管道线,传输数据,

根据下面的图,一直执行上面代码,最终我们找到StandardWrapperValve 

 StandardWrapperValve 这个阀就是最后一个阀门,这个类就是把咱们http服务器请求响应的servlet,执行service方法

 本人工作3年中级菜鸟程序员, 最近想回顾一下知识,做了一些简单总结同时也为了自己今后复习方便,如果有逻辑错误,大家体谅,同时也希望大牛们能给出正确答案让我改正,谢谢!


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM