#0 系列目錄#php
本篇文章將給你們講述Servlet容器中請求處理的過程,在給本篇文章起標題時,一直在「應用服務器」與「Servlet容器」這二者之間拿捏不定,主要是由於要清晰的區分開這二者的關係:Servlet容器能夠說是應用服務器的一個子集。又因爲本文的初衷是講述你們日常使用比較多的Servlet爲主,因此,給本篇就起了《Servlet容器請求處理》的名字。html
先說下在整個WEB請求處理過程當中,本篇文章講述的是哪一個流程模塊。爲直觀明瞭,先上一張圖,紅色部分爲本章所述模塊:前端
所講述的請求流程模塊,你們已經很清楚了。那怎麼給你們去講的更清晰,你們理解的更容易呢?固然是,帶着問題去學習,吸取或許會更快些啦。:)java
開篇以前,給你們提如下幾個問題,這些問題是本文的主體思路(也是我的學習路線):nginx
WEB服務器那麼多,Apache、Tomcat、Nginx、Jetty、Resin,名詞那麼多,HTTP Server、Application Server、Web Server、Servlet Container,他們是什麼?之間關係是什麼?區別又在哪?程序員
CGI、WSGI、Servlet、JSP、FastCGI等等,他們是什麼?他們之間區別又在哪?和上面WEB服務器之間關係是什麼?web
Servlet生命週期及工做原理是什麼?算法
HTTP Request進入到Tomcat中執行,請求處理流程如何?如何找到對應的Application並進行請求處理?數據庫
#1 WEB服務器# 只要Web上的Server都叫Web Server,可是你們分工不一樣,解決的問題也不一樣,因此根據Web Server提供的功能,每一個Web Server的名字也會不同。apache
按功能分類,Web Server能夠分爲:
|- Web Server |- Http Server |- Application Server |- Servlet Container |- CGI Server |- ......
##1.1 Http Server## HTTP Server本質上也是一種應用程序——它一般運行在服務器之上,綁定服務器的IP地址並監聽某一個tcp端口來接收並處理HTTP請求,這樣客戶端(通常來講是IE, Firefox,Chrome這樣的瀏覽器)就可以經過HTTP協議來獲取服務器上的網頁(HTML格式)、文檔(PDF格式)、音頻(MP4格式)、視頻(MOV格式)等等資源。下圖描述的就是這一過程:
一個HTTP Server關心的是HTTP協議層面的傳輸和訪問控制,因此在Apache/Nginx上你能夠看到代理、負載均衡等功能。
- 客戶端經過HTTP Server訪問服務器上存儲的靜態資源(HTML文件、圖片文件等等)。
- 經過CGI/Servlet技術,也能夠將處理過的動態內容經過HTTP Server分發,可是一個HTTP Server始終只是把服務器上的文件如實的經過HTTP協議傳輸給客戶端。
HTTP Server中常用的是Apache、Nginx兩種,HTTP Server主要用來作靜態內容服務、代理服務器、負載均衡等。直面外來請求轉發給後面的應用服務(Tomcat,django什麼的)。
|- Http Server |- Apache |- Nginx
###1.1.1 Apache HTTP服務器### Apache HTTP服務器是一個模塊化的服務器,能夠運行在幾乎全部普遍使用的計算機平臺上。Apache支持模塊多,性能穩定,Apache自己是靜態解析,適合靜態HTML、圖片等,但能夠經過擴展腳本、模塊等支持動態頁面等。
Apache能夠支持PHPcgiperl,可是要使用Java的話,你須要Tomcat在Apache後臺支撐,將Java請求由Apache轉發給Tomcat處理。
###1.1.2 Nginx HTTP服務器### Nginx是一個高性能的HTTP和反向代理服務器,同時也是一個IMAP/POP3/SMTP 代理服務器。
其特色是佔有內存少,併發能力強。Nginx代碼徹底用C語言從頭寫成。
具備很高的穩定性。其它HTTP服務器,當遇到訪問的峯值,或者有人惡意發起慢速鏈接時,也極可能會致使服務器物理內存耗盡頻繁交換,失去響應,只能重啓服務器。例如當前apache一旦上到200個以上進程,web響應速度就明顯很是緩慢了。
而Nginx採起了分階段資源分配技術,使得它的CPU與內存佔用率很是低。Nginx官方表示保持10000個沒有活動的鏈接,它只佔2.5M內存,因此相似DOS這樣的攻擊對nginx來講基本上是毫無用處的。就穩定性而言,Nginx比Lighthttpd更勝一籌。
###1.1.3 Nginx與Apache比較### Nginx相對於Apache的優勢:
- 輕量級,一樣啓動WEB服務,比Apache佔用更少的內存以及資源;
- 抗併發性能高,核心區別在於Apache是同步多進程模型,一個鏈接對應一個進程。Nginx是異步的,多個鏈接(萬級別)能夠對應一個進程;
- Nginx模塊較少,配置簡單,因此Nginx能夠將資源用在數據處理以及進程上面,Apache模塊較多比較全,相對穩定,但在內存資源上消耗比較大;
- Nginx能夠在不間斷的狀況下進行軟件版本的升級;
- Nginx處理靜態頁面性能比apache高3倍多;
選擇高併發高性能就選擇Nginx,若是要穩定,選擇Apache,主要根據服務器要面臨的需求而定。
固然,二者也能夠組合使用:
- Nginx放前端+apache放後端+MYSQL+PHP:能夠提升服務器負載能力
- Nginx處理靜態頁面請求如MP3,GIF.JPG.JS,apache處理動態頁面請求,充分結合了兩者的優點;
##1.2 Application Server## Application Server 是一個應用執行的服務器。它首先須要支持開發語言的 Runtime(對於 Tomcat 來講,就是 Java),保證應用可以在應用服務器上正常運行。其次,須要支持應用相關的規範,例如類庫、安全方面的特性。與HTTP Server相比,Application Server可以動態的生成資源並返回到客戶端。
|- Application Server |- Tomcat |- Jetty
當初在Apache Server開發時還未出現Servlet的概念,因此Apache不能內置支持Servlet。實際上,除了Apache,其餘許多HTTP Server軟件都不能直接支持Servlet。爲了支持Servlet,一般要單獨開發程序,這種程序通常稱爲服務器小程序容器(Servlet Container),有時也叫作服務器小程序引擎(Servlet Engine)。它是Web服務器或應用程序服務器的一部分,用於在發送的請求和響應之上提供網絡服務,解碼基於MIME的請求,格式化基於MIME的響應,它在Servlet的生命週期內包容和管理Servlet,是一個實時運行的外殼程序。運行時由Web服務器軟件處理通常請求,並把Servlet調用傳遞給「容器」來處理。
好比,對於 Tomcat 來講,就是須要提供 JSP/Sevlet 運行須要的標準類庫、Interface 等。爲了方便,應用服務器每每也會集成 HTTP Server 的功能,可是不如專業的 HTTP Server 那麼強大,因此Application Server每每是運行在 HTTP Server 的背後,執行應用,將動態的內容轉化爲靜態的內容以後,經過 HTTP Server 分發到客戶端。
Tomcat運行在JVM之上,它和HTTP服務器同樣,綁定IP地址並監聽TCP端口,同時還包含如下指責:
- 管理Servlet程序的生命週期;
- 將URL映射到指定的Servlet進行處理;
- 與Servlet程序合做處理HTTP請求——根據HTTP請求生成HttpServletRequest/Response對象並傳遞給Servlet進行處理,將Servlet中的HttpServletResponse對象生成的內容返回給瀏覽器;
因此 Tomcat 屬因而一個「Application Server」,可是更準確的來講,是一個「Servlet/JSP」應用的容器(Ruby/Python 等其餘語言開發的應用也沒法直接運行在 Tomcat 上)。
###1.2.1 Servlet容器工做模式### 按照工做模式的不一樣,Servlet容器能夠分爲如下3類:
在這種模式下,Servlet容器做爲構成Web服務器的一部分而存在。當使用基於Java的Web服務器時,就屬於這種狀況。這種方式是Tomcat的默認模式,然而大多數Web服務器並非基於Java的,因此就產生了下面的兩種其餘類型。
Servlet容器由Web服務器插件和Java容器兩部分組成。採用這種方式時,Web服務器插件須要在某個Web服務器內部地址空間中打開一個JVM(Java虛擬機),在此JVM上加載Java容器並運行Servlet。若是客戶端調用Servlet,Web服務器插件首先得到此請求的控制並將它傳遞(使用JNI技術)給Java容器,而後Java容器把此請求交給Servlet來處理。這種方式運行速度較快,而且可以提供良好的性能,適用於單進程、多線程服務器,可是在伸縮性方面存在不足。
採用這種方式時,Servlet容器運行在Web服務器外部地址空間。先由Web服務器插件在某個Web服務器外部地址空間打開一個JVM(Java虛擬機),而後加載Java容器來運行Servlet。Web服務器插件和JVM之間使用IPC(進程間通訊)機制(一般是TCP/IPSockets)。若是客戶端調用Servlet,Web服務器插件首先得到此請求的控制並將它傳遞(使用IPC技術)給Java容器,而後Java容器把此請求交給Servlet來處理。這種方式對客戶端請求的處理速度不如內置Servlet那樣快,可是在其餘方面(如可伸縮性、穩定性等)具備優點。
Tomcat屬於Servlet容器,其工做模式也分爲上述3種,因此Tomcat既可被用做獨立運行的Servlet引擎(便於開發和調試),又可做爲一個須要加強功能的Web服務器(如當前的Apache、IIS和Netscape服務器)插件。在配置Tomcat以前,就須要肯定採用哪一種工做模式,工做模式(1)比較簡單,直接安裝Tomcat便可,工做模式(2)和(3)有些複雜,除了安裝Tomcat、Web服務器以外,還須要安裝鏈接二者的中間鏈接件。
###1.2.2 Apache與Tomcat整合使用### 雖然Tomcat也能夠認爲是HTTP服務器,但一般它仍然會和Apache/Nginx配合在一塊兒使用:
動靜態資源分離——運用Nginx的反向代理功能分發請求:全部動態資源的請求交給Tomcat,而靜態資源的請求(例如圖片、視頻、CSS、JavaScript文件等)則直接由Nginx返回到瀏覽器,這樣能大大減輕Tomcat的壓力;
負載均衡——當業務壓力增大時,可能一個Tomcat的實例不足以處理,那麼這時能夠啓動多個Tomcat實例進行水平擴展,而Nginx的負載均衡功能能夠把請求經過算法分發到各個不一樣的實例進行處理;
整合的好處:
- 若是客戶端請求的是靜態頁面,則只須要Apache服務器響應請求。
- 若是客戶端請求動態頁面,則是Tomcat服務器響應請求。
- 由於JSP是服務器端解釋代碼的,這樣整合就能夠減小Tomcat的服務開銷。
#2 什麼是CGI# 如上文所述,HTTP服務器是一個很簡單的東西,並不負責動態網頁的構建,只能轉發靜態網頁。事物老是不斷髮展,網站也愈來愈複雜,因此出現動態技術。同時Apache也說,它能支持perl,生成動態網頁。這個支持perl,實際上是Apache越位了,作了一件額外的事情。
既然HTTP Server本身不能作,外包給別人吧,可是要與第三作個約定,我給你什麼,而後你給我什麼,就是握把請求參數發送給你,而後我接收你的處理結果給客戶端。那這個約定就是Common Gateway Interface,簡稱CGI。
CGI全稱是「通用網關接口」(Common Gateway Interface),是HTTP服務器與你的或其它機器上的程序進行「交談」的一種工具,其程序須運行在網絡服務器上,是一種根據請求信息動態產生響應內容的接口協議。CGI能夠用任何一種語言編寫,只要這種語言具備標準輸入、輸出和環境變量。如php,perl,tcl等。
經過CGI,HTTP Server能夠將根據請求不一樣啓動不一樣的外部程序,並將請求內容轉發給該程序,在程序執行結束後,將執行結果做爲迴應返回給客戶端。也就是說,對於每一個請求,都要產生一個新的進程進行處理。由於每一個進程都會佔有不少服務器的資源和時間,這就致使服務器沒法同時處理不少的併發請求。另外CGI程序都是與操做系統平臺相關的,雖然在互聯網爆發的初期,CGI爲開發互聯網應用作出了很大的貢獻,可是隨着技術的發展,開始逐漸衰落。
因此,CGI的定義是:外部應用程序與HTTP 服務器之間的接口協議。
##2.1 CGI工做原理## HTTP Server與CGI程序請求處理流程:
HTTP服務器將根據CGI程序的類型決定數據向CGI程序的傳送方式,通常來說是經過標準輸入/輸出流和環境變量來與CGI程序間傳遞數據。 以下圖所示:
**CGI程序經過標準輸入(STDIN)和標準輸出(STDOUT)來進行輸入輸出。**此外CGI程序還經過環境變量來獲得輸入,操做系統提供了許多環境變量,它們定義了程序的執行環境,應用程序能夠存取它們。HTTP服務器和CGI接口又另外設置了一些環境變量,用來向CGI程序傳遞一些重要的參數。CGI的GET方法還經過環境變量QUERY-STRING向CGI程序傳遞Form中的數據。
##2.2 CGI環境變量## 下面是一些經常使用的CGI環境變量:
每當客戶請求CGI的時候,HTTP服務器就請求操做系統生成一個新的CGI解釋器進程(如php-cgi.exe),CGI的一個進程則處理完一個請求後退出,下一個請求來時再建立新進程。固然,這樣在訪問量不多沒有併發的狀況也行。但是當訪問量增大,併發存在,這種方式就不適合了。因而就有了FastCGI。
#3 什麼是FastCGI# FastCGI像是一個常駐(long-live)型的CGI,它能夠一直執行着,只要激活後,不會每次都要花費時間去fork一次(這是CGI最爲人詬病的fork-and-execute 模式)。它還支持分佈式的運算, 即 FastCGI 程序能夠在網站服務器之外的主機上執行而且接受來自其它網站服務器來的請求。
FastCGI是語言無關的、可伸縮架構的CGI開放擴展,其主要行爲是將CGI解釋器進程保持在內存中並所以得到較高的性能。衆所周知,CGI解釋器的反覆加載是CGI性能低下的主要緣由,若是CGI解釋器保持在內存中並接受FastCGI進程管理器調度,則能夠提供良好的性能、伸縮性、Fail- Over特性等等。
##3.1 FastCGI工做原理##
- HTTP Server啓動時載入FastCGI進程管理器(IIS ISAPI或Apache Module);
- FastCGI進程管理器自身初始化,啓動多個CGI解釋器進程(可見多個php-cgi)並等待來自HTTP Server的鏈接;
- 當客戶端請求到達HTTP Server時,FastCGI進程管理器選擇並鏈接到一個CGI解釋器。HTTP Server將CGI環境變量和標準輸入發送到FastCGI子進程php-cgi;
- FastCGI子進程完成處理後將標準輸出和錯誤信息從同一鏈接返回HTTP Server。當FastCGI子進程關閉鏈接時,請求便告處理完成。FastCGI子進程接着等待並處理來自FastCGI進程管理器(運行在HTTP Server中)的下一個鏈接。在CGI模式中,php-cgi在此便退出了。
在上述狀況中,你能夠想象CGI一般有多慢。每個Web請求PHP都必須從新解析php.ini、從新載入所有擴展並重初始化所有數據結構。使用FastCGI,全部這些都只在進程啓動時發生一次。一個額外的好處是,持續數據庫鏈接(Persistent database connection)能夠工做。
##3.2 FastCGI與CGI特色##
#4 什麼是PHP-CGI# PHP-CGI是PHP自帶的FastCGI管理器。PHP-CGI的不足:
- PHP-CGI變動php.ini配置後,需重啓PHP-CGI才能讓新的php-ini生效,不能夠平滑重啓;
- 直接殺死PHP-CGI進程,php就不能運行了。(PHP-FPM和Spawn-FCGI就沒有這個問題,守護進程會平滑重新生成新的子進程。)
#5 什麼是PHP-FPM# PHP-FPM是一個PHP FastCGI管理器,是隻用於PHP的,使用PHP-FPM來控制PHP-CGI的FastCGI進程,它負責管理一個進程池,來處理來自Web服務器的請求。能夠在 http://php-fpm.org/download 下載獲得。
相對Spawn-FCGI,PHP-FPM在CPU和內存方面的控制都更勝一籌,並且前者很容易崩潰,必須用crontab進行監控,而PHP-FPM則沒有這種煩惱。
PHP-FPM提供了更好的PHP進程管理方式,能夠有效控制內存和進程、能夠平滑重載PHP配置,比Spawn-FCGI具備更多優勢,因此被PHP官方收錄了。在PHP 5.3.3中能夠直接使用PHP-FPM了。
在./configure的時候帶 –enable-fpm參數便可開啓PHP-FPM。
##5.1 PHP-FPM工做原理## Apache+PHP配合使用,會在Apache配置下面一段:
LoadModule php5_module C:/php/php5apache2_2.dll
當PHP須要在Apache服務器下運行時,通常來講,它能夠模塊的形式集成,此時模塊的做用是接收Apache傳遞過來的PHP文件請求,並處理這些請求,而後將處理後的結果返回給Apache。若是咱們在Apache啓動前在其配置文件中配置好了PHP模塊,PHP模塊經過註冊apache2的ap_hook_post_config掛鉤,在Apache啓動的時候啓動此模塊以接受PHP文件的請求。
**Apache的Hook機制是指:Apache容許模塊(包括內部模塊和外部模塊,例如mod_php5.so,mod_perl.so等)將自定義的函數注入到請求處理循環中。**換句話說,模塊能夠在Apache的任何一個處理階段中掛接(Hook)上本身的處理函數,從而參與Apache的請求處理過程。mod_php5.so/php5apache2.dll就是將所包含的自定義函數,經過Hook機制注入到Apache中,在Apache處理流程的各個階段負責處理php請求。
有人測試Nginx+PHP-FPM在高併發狀況下可能會達到Apache+mod_php5的5~10倍,如今Nginx+PHP-FPM使用的人愈來愈多。
#6 什麼是Spawn-FCGI# Spawn-FCGI是一個通用的FastCGI管理服務器,它是lighttpd中的一部份,不少人都用Lighttpd的Spawn-FCGI進行FastCGI模式下的管理工做,不過有很多缺點。而PHP-FPM的出現多少緩解了一些問題,但PHP-FPM有個缺點就是要從新編譯,這對於一些已經運行的環境可能有不小的風險(refer)。
Spawn-FCGI目前已經獨成爲一個項目,更加穩定一些,也給不少Web 站點的配置帶來便利。已經有很多站點將它與nginx搭配來解決動態網頁。
##6.1 PHP-FPM與Spawn-CGI對比## PHP-FPM、Spawn-FCGI都是守護PHP-CGI的進程管理器。
PHP-FPM的使用很是方便,配置都是在PHP-FPM.ini的文件內,而啓動、重啓均可以從php/sbin/PHP-FPM中進行。更方便的是修改php.ini後能夠直接使用PHP-FPM reload進行加載,無需殺掉進程就能夠完成php.ini的修改加載。使用PHP-FPM可使PHP有不小的性能提高。PHP-FPM控制的進程CPU回收的速度比較慢,內存分配的很均勻。
Spawn-FCGI控制的進程CPU降低的很快,而內存分配的比較不均勻。有不少進程彷佛未分配到,而另一些卻佔用很高。多是因爲進程任務分配的不均勻致使的。而這也致使了整體響應速度的降低。而PHP-FPM合理的分配,致使整體響應的提到以及任務的平均。
#7 什麼是Servlet# Servlet最初是在1995年由James Gosling提出的,由於使用該技術須要複雜的Web服務器支持,因此當時並無獲得重視,也就放棄了。後來隨着Web應用複雜度的提高,並要求提供更高的併發處理能力,Servlet被從新撿起,並在Java平臺上獲得實現,如今提起Servlet,指的都是Java Servlet。Java Servlet要求必須運行在Web服務器當中,與Web服務器之間屬於分工和互補關係。確切的說,**在實際運行的時候Java Servlet與Web服務器會融爲一體,如同一個程序同樣運行在同一個Java虛擬機(JVM)當中。與CGI不一樣的是,Servlet對每一個請求都是單獨啓動一個線程,而不是進程。**這種處理方式大幅度地下降了系統裏的進程數量,提升了系統的併發處理能力。另外由於Java Servlet是運行在虛擬機之上的,也就解決了跨平臺問題。若是沒有Servlet的出現,也就沒有互聯網的今天。
在Servlet出現以後,隨着使用範圍的擴大,人們發現了它的一個很大的一個弊端。**那就是爲了可以輸出HTML格式內容,須要編寫大量重複代碼,形成沒必要要的重複勞動。**爲了解決這個問題,基於Servlet技術產生了JavaServet Pages技術,也就是JSP。**Servlet和JSP二者分工協做,Servlet側重於解決運算和業務邏輯問題,JSP則側重於解決展現問題。**Servlet與JSP一塊兒爲Web應用開發帶來了巨大的貢獻,後來出現的衆多Java Web應用開發框架都是基於這兩種技術的,更確切的說,都是基於Servlet技術的。
##7.1 Servlet生命週期## 做爲一名專業編程人員,您碰到的大多數 Java servlet 都是爲響應 Web 應用程序上下文中的 HTTP 請求而設計的。**所以,javax.servlet 和 javax.servlet.http 包中特定於 HTTP 的類是您應該關心的。**對於Servlet容器(Tomcat)與HttpServlet是怎樣進行交互的呢,看下類圖:
Servlet的框架是由兩個Java包組成的:javax.servlet與javax.servlet.http。在javax.servlet包中定義了全部的Servlet類都必須實現或者擴展的通用接口和類。在javax.servlet.http包中定義了採用Http協議通訊的HttpServlet類。Servlet的框架的核心是javax.servlet.Servlet接口,全部的Servlet都必須實現這個接口。在Servlet接口中定義了5個方法,其中3個方法表明了Servlet的生命週期:
- init(ServletConfig)方法:負責初始化Servlet對象,在Servlet的生命週期中,該方法執行一次;該方法執行在單線程的環境下,所以開發者不用考慮線程安全的問題;
- service(ServletRequest req,ServletResponse res)方法:負責響應客戶的請求;爲了提升效率,Servlet規範要求一個Servlet實例必須可以同時服務於多個客戶端請求,即service()方法運行在多線程的環境下,Servlet開發者必須保證該方法的線程安全性;
- destroy()方法:當Servlet對象退出生命週期時,負責釋放佔用的資源;
編程注意事項說明:
若是service()方法沒有訪問Servlet的成員變量也沒有訪問全局的資源好比靜態變量、文件、數據庫鏈接等,而是隻使用了當前線程本身的資源,好比非指向全局資源的臨時變量、request和response對象等。該方法自己就是線程安全的,沒必要進行任何的同步控制。
若是service()方法訪問了Servlet的成員變量,可是對該變量的操做是隻讀操做,該方法自己就是線程安全的,沒必要進行任何的同步控制。
若是service()方法訪問了Servlet的成員變量,而且對該變量的操做既有讀又有寫,一般須要加上同步控制語句。
若是service()方法訪問了全局的靜態變量,若是同一時刻系統中也可能有其它線程訪問該靜態變量,若是既有讀也有寫的操做,一般須要加上同步控制語句。
若是service()方法訪問了全局的資源,好比文件、數據庫鏈接等,一般須要加上同步控制語句。
在建立一個 Java servlet 時,通常須要子類 HttpServlet。該類中的方法容許您訪問請求和響應包裝器(wrapper),您能夠用這個包裝器來處理請求和建立響應。大多數程序員都知道Servlet的生命週期,簡單的歸納這就分爲四步:
Servlet類加載--->實例化--->服務--->銷燬;
建立Servlet對象的時機:
注意:在web.xml文件中,某些Servlet只有
<serlvet>
元素,沒有<servlet-mapping>
元素,這樣咱們沒法經過url的方式訪問這些Servlet,這種Servlet一般會在<servlet>
元素中配置一個<load-on-startup>
子元素,讓容器在啓動的時候自動加載這些Servlet並調用init(ServletConfig config)方法來初始化該Servlet。其中方法參數config中包含了Servlet的配置信息,好比初始化參數,該對象由服務器建立。
銷燬Servlet對象的時機:
Servlet容器中止或者從新啓動:Servlet容器調用Servlet對象的destroy方法來釋放資源。以上所講的就是Servlet對象的生命週期。那麼Servlet容器如何知道建立哪個Servlet對象?Servlet對象如何配置?實際上這些信息是經過讀取web.xml配置文件來實現的。
<servlet> <!-- Servlet對象的名稱 --> <servlet-name>action<servlet-name> <!-- 建立Servlet對象所要調用的類 --> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <!-- 參數名稱 --> <param-name>config</param-name> <!-- 參數值 --> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>2</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>2</param-value> </init-param> <!-- Servlet容器啓動時加載Servlet對象的順序 --> <load-on-startup>2</load-on-startup> </servlet> <!-- 要與servlet中的servlet-name配置節內容對應 --> <servlet-mapping> <servlet-name>action</servlet-name> <!-- 客戶訪問的Servlet的相對URL路徑 --> <url-pattern>*.do</url-pattern> </servlet-mapping>
當Servlet容器啓動的時候讀取<servlet>配置節信息,根據<servlet-class>配置節信息建立Servlet對象,同時根據<init-param>配置節信息建立HttpServletConfig對象,而後執行Servlet對象的init方法,而且根據<load-on-startup>配置節信息來決定建立Servlet對象的順序,若是此配置節信息爲負數或者沒有配置,那麼在Servlet容器啓動時,將不加載此Servlet對象。當客戶訪問Servlet容器時,Servlet容器根據客戶訪問的URL地址,經過<servlet-mapping>配置節中的<url-pattern>配置節信息找到指定的Servlet對象,並調用此Servlet對象的service方法。
在整個Servlet的生命週期過程當中,建立Servlet實例、調用實例的init()和destroy()方法都只進行一次,當初始化完成後,Servlet容器會將該實例保存在內存中,經過調用它的service()方法,爲接收到的請求服務。下面給出Servlet整個生命週期過程的UML序列圖,如圖所示:
若是須要讓Servlet容器在啓動時即加載Servlet,能夠在web.xml文件中配置<load-on-startup>元素。
##7.2 Servlet工做原理## 上面描述了Servlet的生命週期,接着咱們描述一下Tomcat與Servlet是如何工做的,首先看下面的時序圖:
- Web Client 向Servlet容器(Tomcat)發出Http請求;
- Servlet容器接收Web Client的請求;
- Servlet容器建立一個HttpRequest對象,將Web Client請求的信息封裝到這個對象中;
- Servlet容器建立一個HttpResponse對象;
- Servlet容器調用HttpServlet對象的service方法,把HttpRequest對象與HttpResponse對象做爲參數傳給 HttpServlet對象;
- HttpServlet調用HttpRequest對象的有關方法,獲取Http請求信息;
- HttpServlet調用HttpResponse對象的有關方法,生成響應數據;
- Servlet容器把HttpServlet的響應結果傳給Web Client;
##7.3 CGI與Servlet比較## CGI應用開發比較困難,由於它要求程序員有處理參數傳遞的知識,這不是一種通用的技能。CGI不可移植,爲某一特定平臺編寫的CGI應用只能運行於這一環境中。每個CGI應用存在於一個由客戶端請求激活的進程中,而且在請求被服務後被卸載。這種模式將引發很高的內存、CPU開銷,並且在同一進程中不能服務多個客戶。
Servlet對CGI的最主要優點在於一個Servlet被客戶端發送的第一個請求激活,而後它將繼續運行於後臺,等待之後的請求。每一個請求將生成一個新的線程,而不是一個完整的進程。多個客戶可以在同一個進程中同時獲得服務。通常來講,Servlet進程只是在Web Server卸載時被卸載。
Servlet提供了Java應用程序的全部優點——可移植、穩健、易開發。使用Servlet Tag技術,Servlet可以生成嵌於靜態HTML頁面中的動態內容。
綜上,Servlet處於服務器進程中,它經過多線程方式運行其service方法,一個實例能夠服務於多個請求,而且其實例通常不會銷燬。 而CGI對每一個請求都產生新的進程,服務完成後就銷燬,因此效率上低於Servlet。
CGI與Servlet的對比:
**對比一:**當用戶瀏覽器發出一個Http/CGI的請求,或者說調用一個CGI程序的時候,服務器端就要新啓用一個進程(並且是每次都要調用),調用CGI程序越多(特別是訪問量高的時候),就要消耗系統越多的處理時間,只剩下愈來愈少的系統資源,對於用戶來講,只能是漫長的等待服務器端的返回頁面了,這對於電子商務激烈發展的今天來講,不能不說是一種技術上的遺憾。
而Servlet充分發揮了服務器端的資源並高效的利用。每次調用Servlet時並非新啓用一個進程,而是在一個Web服務器的進程中共享和分離線程,而線程最大的好處在於能夠共享一個數據源,使系統資源被有效利用。
**對比二:**傳統的CGI程序,不具有平臺無關性特徵,系統環境發生變化,CGI程序就要癱瘓,而Servlet具有Java的平臺無關性,在系統開發過程當中保持了系統的可擴展性、高效性。
對比三:傳統技術中,通常大都爲二層的系統架構,即Web服務器+數據庫服務器,致使網站訪問量大的時候,沒法克服CGI程序與數據庫創建鏈接時速度慢的瓶頸,從而死機、數據庫死鎖現象頻繁發生。而Servlet有鏈接池的概念,它能夠利用多線程的優勢,在系統緩存中事先創建好若干與數據庫的鏈接,到時候若想和數據庫打交道能夠隨時跟系統"要"一個鏈接便可,反應速度可想而知。
#8 Tomcat工做原理# Tomcat 的結構很複雜,可是 Tomcat 也很是的模塊化,找到了 Tomcat 最核心的模塊,您就抓住了 Tomcat 的「七寸」。下面是 Tomcat 的整體結構圖:
從上圖能夠看出Tomcat的核心是兩個組件:鏈接器(Connector)和容器(Container)。Connector組件是負責生成請求對象和響應對象的,Tomcat默認的是HttpConnector,負責根據收到的Http請求報文生成Request對象和Response對象,並把這兩個對象傳遞給Container,而後根據Response中的內容生成相應的HTTP報文。
Container是容器的父接口,全部子容器都必須實現這個接口,簡單來講就是服務器部署的項目是運行在Container中的。Container裏面的項目獲取到Connector傳遞過來對應的的Request對象和Response對象進行相應的操做。
Connector能夠根據不一樣的設計和應用場景進行替換。一個Container能夠選擇對應多個Connector。多個Connector和一個Container就造成了一個Service,有了Service就能夠對外提供服務了。
Tomcat要爲一個Servlet的請求提供服務,須要作三件事:
- 建立一個request對象並填充那些有可能被所引用的Servlet使用的信息,如參數,頭部、cookies、查詢字符串等。一個request對象就是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一個實例。
- 建立一個response對象,所引用的servlet使用它來給客戶端發送響應。一個response對象是javax.servlet.ServletResponse或javax.servlet.http.ServletResponse接口的一個實例。
- 調用servlet的service方法,並傳入request和response對象。這裏servlet會從request對象取值,給response寫值。
- 根據servlet返回的response生成相應的HTTP響應報文。
既然咱們已經抓到Tomcat的「七寸」,兩個核心組件:鏈接器(Connector)和容器(Container),那這樣從鏈接器(Connector)入手,來看下Tomcat處理HTTP請求的流程。
不少開源應用服務器都是集成tomcat做爲web container的,並且對於tomcat的servlet container這部分代碼不多改動。這樣,這些應用服務器的性能基本上就取決於Tomcat處理HTTP請求的connector模塊的性能。
##8.1 Connector種類## Tomcat源碼中與connector相關的類位於org.apache.coyote包中,Connector分爲如下幾類:
Http Connector,基於HTTP協議,負責創建HTTP鏈接。它又分爲BIO Http Connector與NIO Http Connector兩種,後者提供非阻塞IO與長鏈接Comet支持。
AJP Connector,基於AJP協議,AJP是專門設計用來爲tomcat與http服務器之間通訊專門定製的協議,能提供較高的通訊速度和效率。如與Apache服務器集成時,採用這個協議。
APR HTTP Connector,用C實現,經過JNI調用的。主要提高對靜態資源(如HTML、圖片、CSS、JS等)的訪問性能。如今這個庫已獨立出來可用在任何項目中。Tomcat在配置APR以後性能很是強勁。
##8.2 Connector配置## 對Connector的配置位於conf/server.xml文件中。
###8.2.1 BIO HTTP/1.1 Connector配置###
<Connector port=」8080」 protocol=」HTTP/1.1」 maxThreads=」150」 connectionTimeout=」20000」 redirectPort=」8443」 />
其它一些重要屬性以下:
acceptCount : 接受鏈接request的最大鏈接數目,默認值是10;
address : 綁定IP地址,若是不綁定,默認將綁定任何IP地址;
allowTrace : 若是是true,將容許TRACE HTTP方法;
compressibleMimeTypes : 各個mimeType, 以逗號分隔,如text/html,text/xml;
compression : 若是帶寬有限的話,能夠用GZIP壓縮;
connectionTimeout : 超時時間,默認爲60000ms (60s);
maxKeepAliveRequest : 默認值是100;
maxThreads : 處理請求的Connector的線程數目,默認值爲200;
若是是SSL配置,以下:
<Connector port="8181" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol = "TLS" address="0.0.0.0" keystoreFile="E:/java/jonas-full-5.1.0-RC3/conf/keystore.jks" keystorePass="changeit" />
其中,keystoreFile爲證書位置,keystorePass爲證書密碼。
###8.2.2 NIO HTTP/1.1 Connector配置###
<Connector port=」8080」 protocol=」org.apache.coyote.http11.Http11NioProtocol」 maxThreads=」150」 connectionTimeout=」20000」 redirectPort=」8443」 />
###8.2.3 Native APR Connector配置###
下載地址是:http://tomcat.heanet.ie/native/1.1.10/binaries/win32/
<!--APR library loader. Documentation at /docs/apr.html --> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Connector port=」8080」 protocol=」org.apache.coyote.http11.Http11AprProtocol」 maxThreads=」150」 connectionTimeout=」20000」 redirectPort=」8443」 />
org.apache.coyote.http11.Http11AprProtocol init
##8.3 Tomcat架構模塊##
- Server(服務器)是Tomcat構成的頂級構成元素,全部一切均包含在Server中,Server的實現類StandardServer能夠包含一個到多個Services;
- 次頂級元素Service的實現類爲StandardService調用了容器(Container)接口,實際上是調用了Servlet Engine(引擎),並且StandardService類中也指明瞭該Service歸屬的Server;
- 接下來次級的構成元素就是容器(Container):主機(Host)、上下文(Context)和引擎(Engine)均繼承自Container接口,因此它們都是容器。可是,它們是有父子關係的,在主機(Host)、上下文(Context)和引擎(Engine)這三類容器中,引擎是頂級容器,直接包含是主機容器,而主機容器又包含上下文容器,因此引擎、主機和上下文從大小上來講又構成父子關係,雖然它們都繼承自Container接口。
- 鏈接器(Connector)將Service和Container鏈接起來,首先它須要註冊到一個Service,它的做用就是把來自客戶端的請求轉發到Container(容器),這就是它爲何稱做鏈接器的緣由。
##8.4 Tomcat運行流程##
假設來自客戶的請求爲:http://localhost:8080/test/index.jsp
- 請求被髮送到本機端口8080,被在那裏偵聽的Coyote HTTP/1.1 Connector得到;
- Connector把該請求交給它所在的Service的Engine來處理,並等待Engine的迴應;
- Engine得到請求localhost:8080/test/index.jsp,匹配它全部虛擬主機Host;
- Engine匹配到名爲localhost的Host(即便匹配不到也把請求交給該Host處理,由於該Host被定義爲該Engine的默認主機);
- localhost Host得到請求/test/index.jsp,匹配它所擁有的全部Context;
- Host匹配到路徑爲/test的Context(若是匹配不到就把該請求交給路徑名爲""的Context去處理);
- path="/test"的Context得到請求/index.jsp,在它的mapping table中尋找對應的servlet;
- Context匹配到URL PATTERN爲*.jsp的servlet,對應於JspServlet類;
- 構造HttpServletRequest對象和HttpServletResponse對象,做爲參數調用JspServlet的doGet或doPost方法;
- Context把執行完了以後的HttpServletResponse對象返回給Host;
- Host把HttpServletResponse對象返回給Engine;
- Engine把HttpServletResponse對象返回給Connector;
- Connector把HttpServletResponse對象返回給客戶browser;