Tomcat服務(wù)器配置參考 ?
?
|
[ Tomcat 源碼系列] Tomcat Connector
一、連接器介紹
在開始Connector探索之路之前,先看看Connector幾個關(guān)鍵字
- NIO: Tomcat 可以利用Java比較新的NIO技術(shù),提升高并發(fā)下的Socket性能
- AJP:Apache JServ Protocol,AJP的提出當(dāng)然還是為了解決java亙古不變的問題——性能,AJP協(xié)議是基于包的長連接協(xié)議,以減少前端Proxy與 Tomcat 連接Socket連接創(chuàng)建的代價(jià),目前Apache通過JK和AJP_ROXY的方式支持AJP協(xié)議,需要注意的是,雖然Nginx作為代理服務(wù)器性能強(qiáng)勁,但其只能通過HTTP PROXY的方式與后端的 Tomcat 聯(lián)系,因此如果從作為代理服務(wù)器的角度上講,在這種情況下Nginx未必會比Apache體現(xiàn)出更優(yōu)的性能
- APR/Native:Apache Portable Runtime,還是一個詞,性能。APR的提出利用Native代碼更好地解決性能問題,更好地與本地服務(wù)器(linux)打交道。讓我們看看 Tomcat 文檔對APR的介紹
通過對如上名詞的組合, Tomcat 組成了如下的Connector系列:
- Http11Protocol:支持HTTP1.1協(xié)議的連接器
- Http11NioProtocol:支持HTTP1.1 協(xié)議+ NIO的連接器
- Http11AprProtocol:使用APR技術(shù)處理連接的連接器
- AjpProtocol:支持AJP協(xié)議的連接器
- AjpAprProtocol:使用APR技術(shù)處理連接的連接器
二、范例 ????? 我們以最簡單的Http11Protocol為例,看看從請求進(jìn)來到處理完畢,連接器部件是處理處理的。首先我們利用 Tomcat 組件組成我們一個最簡單的WebServer,其具備如下功能:
- 監(jiān)停某個端口,接受客戶端的請求,并將請求分配給處理線程
- 處理線程處理請求,分析HTTP1.1請求,封裝Request/Response對象,并將請求由請求處理器處理
- 實(shí)現(xiàn)最簡單的請求處理器,向客戶端打印Hello World
代碼非常簡單,首先是主功能(這里,我們利用JDK5.0的線程池,連接器不再管理線程功能):
- package ray. tomcat .test;??
- ?
- import java.util.concurrent.BlockingQueue;??
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;??
- ?
- import org.apache.coyote.http11.Http11Protocol;
- ?
- public class Tomcat MainV2??
- {??
- ??? public static void main(String[] args) throws Exception??
- ??? {??
- ??????? Http11Protocol protocol = new Http11Protocol();
- ??????? protocol.setPort( 8000 );??
- ??????? ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();
- ??????? threadPoolExecutor.prestartCoreThread();
- ??????? protocol.setExecutor(threadPoolExecutor);
- ??????? protocol.setAdapter( new MyHandler());
- ??????? protocol.init();??
- ??????? protocol.start();??
- ??? }??
- ?
- ??? public static ThreadPoolExecutor createThreadPoolExecutor()
- ??? {??
- ??????? int corePoolSize = 2 ;
- ??????? int maximumPoolSize = 10 ;??
- ??????? long keepAliveTime = 60 ;??
- ??????? TimeUnit unit = TimeUnit.SECONDS;??
- ??????? BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();??
- ??????? ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
- ??????????????? corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
- ??????? return threadPoolExecutor;??
- ??? }??
- }?
請求處理器向客戶端打引Hello World,代碼如下
- package ray. tomcat .test;??
- ?
- import java.io.ByteArrayOutputStream;??
- import java.io.OutputStreamWriter;??
- import java.io.PrintWriter;??
- ?
- import org.apache.coyote.Adapter;??
- import org.apache.coyote.Request;??
- import org.apache.coyote.Response;??
- import org.apache. tomcat .util.buf.ByteChunk;??
- import org.apache. tomcat .util.net.SocketStatus;??
- ?
- public class MyHandler implements Adapter
- {??
- ??? //支持Comet,Servlet3.0將對Comet提供支持, Tomcat 6目前是非標(biāo)準(zhǔn)的實(shí)現(xiàn) ?
- ??? public boolean event(Request req, Response res, SocketStatus status)
- ??????????? throws Exception??
- ??? {??
- ??????? System.out.println( "event" );??
- ??????? return false ;??
- ??? }??
- ?
- ??? //請求處理 ?
- ??? public void service(Request req, Response res) throws Exception??
- ??? {??
- ??????? System.out.println( "service" );??
- ?
- ??????? ByteArrayOutputStream baos = new ByteArrayOutputStream();??
- ??????? PrintWriter writer = new PrintWriter( new OutputStreamWriter(baos));??
- ??????? writer.println( "Hello World" );??
- ??????? writer.flush();??
- ?
- ??????? ByteChunk byteChunk = new ByteChunk();??
- ??????? byteChunk.append(baos.toByteArray(), 0 , baos.size());??
- ??????? res.doWrite(byteChunk);??
- ??? }?????
- }?
???? 運(yùn)行主程序,在瀏覽器中輸入http://127.0.0.1:8000,我們可以看到打印”Hello World” 三、分析 ???? 以如上Http11Protocol為例,我們可以看到, Tomcat 實(shí)現(xiàn)一個最簡單的處理Web請求的代碼其實(shí)非常簡單,其主要包括如下核心處理類:
- Http11Protocol:Http1.1協(xié)議處理入口類,其本身沒有太多邏輯,對請求主要由JIoEndPoint類處理
- Http11Protocol$Http11ConnectionHandler:連接管理器,管理連接處理隊(duì)列,并分配Http11Processor對請求進(jìn)行處理
- Http11Processor:請求處理器,負(fù)責(zé)HTTP1.0協(xié)議相關(guān)的工作,包括解析請求和處理響應(yīng)信息,并調(diào)用Adapter做實(shí)際的處理工作,如上我們看到了我們自定義的Adapter實(shí)現(xiàn)響應(yīng)”Hello World”
- JIoEndPoint:監(jiān)停端口,啟動接受線程準(zhǔn)備接收請求,在請求接受后轉(zhuǎn)給工作線程處理
- JIoEndPoint$Acceptor:請求接收器,接收后將Socket分配給工作線程繼續(xù)后續(xù)處理
- JIoEndPoint$Worker:工作線程,使用Handler來處理請求,對于我們的HTTP1.1協(xié)議來說,其實(shí)現(xiàn)是Http11Protocol$Http11ConnectionHandler。這部分不是必須的,也可以選擇JDK的concurrent包的線程池
????? 實(shí)際上各種連接器實(shí)現(xiàn)基本大同小異,基本上都是由如上部分組合而成
????? 1.初始化:首先,還是從入口開始,先看看初始化init
- public void init() throws Exception {
- ??????? endpoint.setName(getName());??
- ??????? endpoint.setHandler(cHandler); //請求處理器,對于HTTP1.1協(xié)議,是Http11Protocol$Http11ConnectionHandler ?
- ?
- ?????????
- // 初始化ServerSocket工廠類,如果需SSL/TLS支持,使用JSSESocketFactory/PureTLSSocketFactory ?
- . . . (略)?????????
- ????????????? //主要的初始化過程實(shí)際是在endpoint(JIoEndpoint) ?
- ??????? endpoint.init();??
- ??????? . . . (略)??
- ??? }?
????? Http11Protocol的初始化非常簡單,準(zhǔn)備好ServerSocket工廠,調(diào)用JIoEndPoint的初始化。讓我們接下來看看JIoEndPoint的初始化過程
- public void init()??
- ??????? throws Exception {??
- ?
- ??????? if (initialized)??
- ??????????? return ;??
- ?????????
- ??????? // Initialize thread count defaults for acceptor ?
- ??????? // 請求接收處理線程,這個值實(shí)際1已經(jīng)足夠 ?
- ??????? if (acceptorThreadCount == 0 ) {??
- ??????????? acceptorThreadCount = 1 ;??
- ??????? }??
- ??????? if (serverSocketFactory == null ) {??
- ??????????? serverSocketFactory = ServerSocketFactory.getDefault();
- ??????? }??
- ?????????
- ??????? //創(chuàng)建監(jiān)停ServerSocket,port為監(jiān)聽端口,address為監(jiān)停地址 ?
- ??????? // backlog為連接請求隊(duì)列容量(@param backlog how many connections are queued) ?
- ??????? if (serverSocket == null ) {??
- ??????????? try {??
- ??????????????? if (address == null ) {??
- ??????????????????? serverSocket = serverSocketFactory.createSocket(port, backlog);
- ??????????????? } else {??
- ??????????????????? serverSocket = serverSocketFactory.createSocket(port, backlog, address);
- ??????????????? }??
- ??????????? } catch (BindException be) {??
- ??????????????? throw new BindException(be.getMessage() + ":" + port);??
- ??????????? }??
- ??????? }??
- ??????? //if( serverTimeout >= 0 ) ?
- ??????? //??? serverSocket.setSoTimeout( serverTimeout ); ?
- ?????????
- ??????? initialized = true ;????
- ??? }?
??????? 可以看到,監(jiān)停端口在此處準(zhǔn)備就緒
- public void start()??
- ???? throws Exception {??
- ???? // Initialize socket if not done before ?
- ???? if (!initialized) {??
- ???????? init();??
- ???? }??
- ???? if (!running) {??
- ???????? running = true ;??
- ???????? paused = false ;??
- ?
- ???????? // Create worker collection ?
- ???????? // 初始化工作線程池,有WorkerStack( Tomcat 自實(shí)現(xiàn))和Executor(JDK concurrent包)兩種實(shí)現(xiàn) ?
- ???????? if (executor == null ) {??
- ???????????? workers = new WorkerStack(maxThreads);??
- ???????? }??
- ?
- ???????? // Start acceptor threads ?
- ???????? // 啟動請求連接接收處理線程 ?
- ???????? for ( int i = 0 ; i < acceptorThreadCount; i++) {??
- ???????????? Thread acceptorThread = new Thread( new Acceptor(), getName() + "-Acceptor-" + i);??
- ???????????? acceptorThread.setPriority(threadPriority);
- ???????????? acceptorThread.setDaemon(daemon); //設(shè)置是否daemon參數(shù),默認(rèn)為true ?
- ???????????? acceptorThread.start();??
- ???????? }??
- ???? }??
- }?
????? 2.準(zhǔn)備好連接處理:初始化完畢,準(zhǔn)備好連接處理,準(zhǔn)備接收連接上來,同樣的,Http11Protocol的start基本沒干啥事,調(diào)用一下JIoEndPoint的start,我們來看看JIoEndPoint的start
- public void start()??
- ???? throws Exception {??
- ???? // Initialize socket if not done before ?
- ???? if (!initialized) {??
- ???????? init();??
- ???? }??
- ???? if (!running) {??
- ???????? running = true ;??
- ???????? paused = false ;??
- ?
- ???????? // Create worker collection ?
- ???????? // 初始化工作線程池,有WorkerStack( Tomcat 自實(shí)現(xiàn))和Executor(JDK concurrent包)兩種實(shí)現(xiàn) ?
- ???????? if (executor == null ) {??
- ???????????? workers = new WorkerStack(maxThreads);??
- ???????? }??
- ?
- ???????? // Start acceptor threads ?
- ???????? // 啟動請求連接接收處理線程 ?
- ???????? for ( int i = 0 ; i < acceptorThreadCount; i++) {??
- ???????????? Thread acceptorThread = new Thread( new Acceptor(), getName() + "-Acceptor-" + i);??
- ???????????? acceptorThread.setPriority(threadPriority);
- ???????????? acceptorThread.setDaemon(daemon); //設(shè)置是否daemon參數(shù),默認(rèn)為true ?
- ???????????? acceptorThread.start();??
- ???????? }??
- ???? }??
- }?
????? 主要處理的事情無非就是準(zhǔn)備和工作線程(處理具體請求的線程度池,可選,也可以使用JDK5.0的線程池),連接請求接收處理線程(代碼中,一般acceptorThreadCount=1)
????? 3.連接請求接收處理:準(zhǔn)備就緒,可以連接入請求了。現(xiàn)在工作已經(jīng)轉(zhuǎn)到了Acceptor(JIoEndPoint$Acceptor)這里,我們看看Acceptor到底做了些啥
- public void run() {??
- ?
- ??????????? // Loop until we receive a shutdown command ?
- ??????????? while (running) {
- ??????????????? . . . (略)??
- ??????????????????? //阻塞等待客戶端連接 ?
- ??????????????????? Socket socket = serverSocketFactory.acceptSocket(serverSocket);
- ??????????????????? serverSocketFactory.initSocket(socket);
- ??????????????????? // Hand this socket off to an appropriate processor ?
- ??????????????????? if (!processSocket(socket)) {??
- ??????????????????????? // Close socket right away ?
- ??????????????????????? try {??
- ??????????????????????????? socket.close();
- ??????????????????????? } catch (IOException e) {
- ??????????????????????????? // Ignore ?
- ??????????????????????? }??
- ??????????????????? }??
- ????????????????? . . . (略)??
- ??????????? }??
- }??
- . . . (略)??
- ??? protected boolean processSocket(Socket socket) {??
- ??????? try {??
- ??????????? //由工作線程繼續(xù)后續(xù)的處理 ?
- ??????????? if (executor == null ) {??
- ??????????????? getWorkerThread().assign(socket);
- ??????????? } else {??
- ??????????????? executor.execute( new SocketProcessor(socket));??
- ??????????? }??
- ??????? } catch (Throwable t) {??
- ??????????? . . . (略)?????????????
- return false ;??
- ??????? }??
- ??????? return true ;??
- ??? }?
?????? 實(shí)際上也沒有什么復(fù)雜的工作,無非就是有連接上來之后,將連接轉(zhuǎn)交給工作線程(SocketProcessor)去處理
?????? 4.工作線程:SocketProcessor
- public void run() {??
- ??????????? // Process the request from this socket ?
- ??????????? if (!setSocketOptions(socket) || !handler.process(socket)) {
- ??????????????? // Close socket ?
- ??????????????? try {??
- ??????????????????? socket.close();??
- ??????????????? } catch (IOException e) {
- ??????????????? }??
- ??????????? }??
- ??????????? // Finish up this request ?
- ??????????? socket = null ;??
- ??????? }????
??? 工作線程主要是設(shè)置一下Socket參數(shù),然后將請求轉(zhuǎn)交給handler去處理,需要注意一下如下幾個連接參數(shù)的意義:
- SO_LINGER:若設(shè)置了SO_LINGER并確定了非零的超時間隔,則closesocket()調(diào)用阻塞進(jìn)程,直到所剩數(shù)據(jù)發(fā)送完畢或超時。這種關(guān)閉稱為“優(yōu)雅的”關(guān) 閉。請注意如果套接口置為非阻塞且SO_LINGER設(shè)為非零超時,則closesocket()調(diào)用將以WSAEWOULDBLOCK錯誤返回。若在一個流類套接口上設(shè)置了SO_DONTLINGER,則closesocket()調(diào)用立即返回。但是,如果可能,排隊(duì)的數(shù)據(jù)將在套接口關(guān)閉前發(fā)送。請注意,在這種情況下WINDOWS套接口實(shí)現(xiàn)將在 一段不確定的時間內(nèi)保留套接口以及其他資源(TIME_WAIT),這對于想用所以套接口的應(yīng)用程序來說有一定影響。默認(rèn)此參數(shù)不打開
- TCP_NODELAY:是否打開Nagle,默認(rèn)打開,使用Nagle算法是為了避免多次發(fā)送小的分組,而是累計(jì)到一定程度或者超過一定時間后才一起發(fā)送。對于AJP連接,可能需要關(guān)注一下這個選項(xiàng)。
- SO_TIMEOUT:JDK API注釋如下,With this option set to a non-zero timeout,a read() call on the InputStream associated with this Socket will block for only this amount of time.? If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0。默認(rèn)設(shè)置的是60秒
??????? 關(guān)于默認(rèn)的設(shè)置,可以參見org.apache.coyote.http11.Constants定義 ????? 5.最終請求終于回到了Handler,此處的Handler實(shí)現(xiàn)是org.apache.coyote.http11.Http11Processor,其主要處理一些HTTP協(xié)議性細(xì)節(jié)的東西,此處代碼不再列出,有興趣可以自行讀代碼。最終請求終于回到了我們的Adapter對象,一個請求處理完畢,功德圓滿。
來源: http://1632004.blog.163.com/blog/static/29991497201201912858468/
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
