????? Connector是Tomcat最核心的組件之一,負責處理一個WebServer最核心的連接管理、Net IO、線程(可選)、協(xié)議解析和處理的工作。
一、連接器介紹
在開始Connector探索之路之前,先看看Connector幾個關鍵字
- NIO:Tomcat可以利用Java比較新的NIO技術,提升高并發(fā)下的Socket性能
- AJP:Apache JServ Protocol,AJP的提出當然還是為了解決java亙古不變的問題——性能,AJP協(xié)議是基于包的長連接協(xié)議,以減少前端Proxy與Tomcat連接Socket連接創(chuàng)建的代價,目前Apache通過JK和AJP_ROXY的方式支持AJP協(xié)議,需要注意的是,雖然Nginx作為代理服務器性能強勁,但其只能通過HTTP PROXY的方式與后端的Tomcat聯(lián)系,因此如果從作為代理服務器的角度上講,在這種情況下Nginx未必會比Apache體現(xiàn)出更優(yōu)的性能
- APR/Native:Apache Portable Runtime,還是一個詞,性能。APR的提出利用Native代碼更好地解決性能問題,更好地與本地服務器(linux)打交道。讓我們看看Tomcat文檔對APR的介紹
These features allows making Tomcat a general purpose webserver, will enable much better integration with other native web technologies, and overall make Java much more viable as a full fledged webserver platform rather than simply a backend focused technology.
通過對如上名詞的組合,Tomcat組成了如下的Connector系列:
- Http11Protocol:支持HTTP1.1協(xié)議的連接器
- Http11NioProtocol:支持HTTP1.1 協(xié)議+ NIO的連接器
- Http11AprProtocol:使用APR技術處理連接的連接器
- AjpProtocol:支持AJP協(xié)議的連接器
- AjpAprProtocol:使用APR技術處理連接的連接器
二、范例
????? 我們以最簡單的Http11Protocol為例,看看從請求進來到處理完畢,連接器部件是處理處理的。首先我們利用Tomcat組件組成我們一個最簡單的WebServer,其具備如下功能:
- 監(jiān)停某個端口,接受客戶端的請求,并將請求分配給處理線程
- 處理線程處理請求,分析HTTP1.1請求,封裝Request/Response對象,并將請求由請求處理器處理
- 實現(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?TomcatMainV2??
- {??
- ???? 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提供支持,Tomcat6目前是非標準的實現(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);??
- ????}?????
- }??
???? 運行主程序,在瀏覽器中輸入http://127.0.0.1:8000,我們可以看到打印”Hello World”
三、分析
???? 以如上Http11Protocol為例,我們可以看到,Tomcat實現(xiàn)一個最簡單的處理Web請求的代碼其實非常簡單,其主要包括如下核心處理類:
- Http11Protocol:Http1.1協(xié)議處理入口類,其本身沒有太多邏輯,對請求主要由JIoEndPoint類處理
- Http11Protocol$Http11ConnectionHandler:連接管理器,管理連接處理隊列,并分配Http11Processor對請求進行處理
- Http11Processor:請求處理器,負責HTTP1.0協(xié)議相關的工作,包括解析請求和處理響應信息,并調用Adapter做實際的處理工作,如上我們看到了我們自定義的Adapter實現(xiàn)響應”Hello World”
- JIoEndPoint:監(jiān)停端口,啟動接受線程準備接收請求,在請求接受后轉給工作線程處理
- JIoEndPoint$Acceptor:請求接收器,接收后將Socket分配給工作線程繼續(xù)后續(xù)處理
- JIoEndPoint$Worker:工作線程,使用Handler來處理請求,對于我們的HTTP1.1協(xié)議來說,其實現(xiàn)是Http11Protocol$Http11ConnectionHandler。這部分不是必須的,也可以選擇JDK的concurrent包的線程池
????? 實際上各種連接器實現(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??
- .?.?.?(略)?????????
- ?????????????? //主要的初始化過程實際是在endpoint(JIoEndpoint)??
- ????????endpoint.init();??
- ????????.?.?.?(略)??
- ????}??
????? Http11Protocol的初始化非常簡單,準備好ServerSocket工廠,調用JIoEndPoint的初始化。讓我們接下來看看JIoEndPoint的初始化過程
- public? void?init()??
- ???????? throws?Exception?{??
- ??
- ???????? if?(initialized)??
- ???????????? return;??
- ?????????
- ???????? //?Initialize?thread?count?defaults?for?acceptor??
- ???????? //?請求接收處理線程,這個值實際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為連接請求隊列容量(@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)停端口在此處準備就緒
- 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自實現(xiàn))和Executor(JDK?concurrent包)兩種實現(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);? //設置是否daemon參數(shù),默認為true??
- ?????????????acceptorThread.start();??
- ?????????}??
- ?????}??
- ?}??
????? 2.準備好連接處理:初始化完畢,準備好連接處理,準備接收連接上來,同樣的,Http11Protocol的start基本沒干啥事,調用一下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自實現(xiàn))和Executor(JDK?concurrent包)兩種實現(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);? //設置是否daemon參數(shù),默認為true??
- ?????????????acceptorThread.start();??
- ?????????}??
- ?????}??
- ?}??
????? 主要處理的事情無非就是準備和工作線程(處理具體請求的線程度池,可選,也可以使用JDK5.0的線程池),連接請求接收處理線程(代碼中,一般acceptorThreadCount=1)
????? 3.連接請求接收處理:準備就緒,可以連接入請求了?,F(xiàn)在工作已經(jīng)轉到了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;??
- ????}??
?????? 實際上也沒有什么復雜的工作,無非就是有連接上來之后,將連接轉交給工作線程(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;??
- ????????}?????
??? 工作線程主要是設置一下Socket參數(shù),然后將請求轉交給handler去處理,需要注意一下如下幾個連接參數(shù)的意義:
- SO_LINGER:若設置了SO_LINGER并確定了非零的超時間隔,則closesocket()調用阻塞進程,直到所剩數(shù)據(jù)發(fā)送完畢或超時。這種關閉稱為“優(yōu)雅的”關 閉。請注意如果套接口置為非阻塞且SO_LINGER設為非零超時,則closesocket()調用將以WSAEWOULDBLOCK錯誤返回。若在一個流類套接口上設置了SO_DONTLINGER,則closesocket()調用立即返回。但是,如果可能,排隊的數(shù)據(jù)將在套接口關閉前發(fā)送。請注意,在這種情況下WINDOWS套接口實現(xiàn)將在 一段不確定的時間內保留套接口以及其他資源(TIME_WAIT),這對于想用所以套接口的應用程序來說有一定影響。默認此參數(shù)不打開
- TCP_NODELAY:是否打開Nagle,默認打開,使用Nagle算法是為了避免多次發(fā)送小的分組,而是累計到一定程度或者超過一定時間后才一起發(fā)送。對于AJP連接,可能需要關注一下這個選項。
- 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。默認設置的是60秒
??????? 關于默認的設置,可以參見org.apache.coyote.http11.Constants定義
????? 5.最終請求終于回到了Handler,此處的Handler實現(xiàn)是org.apache.coyote.http11.Http11Processor,其主要處理一些HTTP協(xié)議性細節(jié)的東西,此處代碼不再列出,有興趣可以自行讀代碼。最終請求終于回到了我們的Adapter對象,一個請求處理完畢,功德圓滿。
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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