《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