Tomcat服務器配置參考
|
[Tomcat源碼系列] Tomcat Connector
一、連接器介紹
在開始Connector探索之路之前,先看看Connector幾個關鍵字
- NIO:Tomcat可以利用Java比較新的NIO技術,提升高並發下的Socket性能
- AJP:Apache JServ Protocol,AJP的提出當然還是為了解決java亘古不變的問題——性能,AJP協議是基於包的長連接協議,以減少前端Proxy與Tomcat連接Socket連接創建的代價,目前Apache通過JK和AJP_ROXY的方式支持AJP協議,需要注意的是,雖然Nginx作為代理服務器性能強勁,但其只能通過HTTP PROXY的方式與后端的Tomcat聯系,因此如果從作為代理服務器的角度上講,在這種情況下Nginx未必會比Apache體現出更優的性能
- APR/Native:Apache Portable Runtime,還是一個詞,性能。APR的提出利用Native代碼更好地解決性能問題,更好地與本地服務器(linux)打交道。讓我們看看Tomcat文檔對APR的介紹
通過對如上名詞的組合,Tomcat組成了如下的Connector系列:
- Http11Protocol:支持HTTP1.1協議的連接器
- Http11NioProtocol:支持HTTP1.1 協議+ NIO的連接器
- Http11AprProtocol:使用APR技術處理連接的連接器
- AjpProtocol:支持AJP協議的連接器
- AjpAprProtocol:使用APR技術處理連接的連接器
二、范例 我們以最簡單的Http11Protocol為例,看看從請求進來到處理完畢,連接器部件是處理處理的。首先我們利用Tomcat組件組成我們一個最簡單的WebServer,其具備如下功能:
- 監停某個端口,接受客戶端的請求,並將請求分配給處理線程
- 處理線程處理請求,分析HTTP1.1請求,封裝Request/Response對象,並將請求由請求處理器處理
- 實現最簡單的請求處理器,向客戶端打印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目前是非標准的實現
- 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實現一個最簡單的處理Web請求的代碼其實非常簡單,其主要包括如下核心處理類:
- Http11Protocol:Http1.1協議處理入口類,其本身沒有太多邏輯,對請求主要由JIoEndPoint類處理
- Http11Protocol$Http11ConnectionHandler:連接管理器,管理連接處理隊列,並分配Http11Processor對請求進行處理
- Http11Processor:請求處理器,負責HTTP1.0協議相關的工作,包括解析請求和處理響應信息,並調用Adapter做實際的處理工作,如上我們看到了我們自定義的Adapter實現響應”Hello World”
- JIoEndPoint:監停端口,啟動接受線程准備接收請求,在請求接受后轉給工作線程處理
- JIoEndPoint$Acceptor:請求接收器,接收后將Socket分配給工作線程繼續后續處理
- JIoEndPoint$Worker:工作線程,使用Handler來處理請求,對於我們的HTTP1.1協議來說,其實現是Http11Protocol$Http11ConnectionHandler。這部分不是必須的,也可以選擇JDK的concurrent包的線程池
實際上各種連接器實現基本大同小異,基本上都是由如上部分組合而成
1.初始化:首先,還是從入口開始,先看看初始化init
- public void init() throws Exception {
- endpoint.setName(getName());
- endpoint.setHandler(cHandler); //請求處理器,對於HTTP1.1協議,是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已經足夠
- if (acceptorThreadCount == 0) {
- acceptorThreadCount = 1;
- }
- if (serverSocketFactory == null) {
- serverSocketFactory = ServerSocketFactory.getDefault();
- }
- //創建監停ServerSocket,port為監聽端口,address為監停地址
- // 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;
- }
可以看到,監停端口在此處准備就緒
- 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自實現)和Executor(JDK concurrent包)兩種實現
- 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參數,默認為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自實現)和Executor(JDK concurrent包)兩種實現
- 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參數,默認為true
- acceptorThread.start();
- }
- }
- }
主要處理的事情無非就是准備和工作線程(處理具體請求的線程度池,可選,也可以使用JDK5.0的線程池),連接請求接收處理線程(代碼中,一般acceptorThreadCount=1)
3.連接請求接收處理:准備就緒,可以連接入請求了。現在工作已經轉到了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 {
- //由工作線程繼續后續的處理
- 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參數,然后將請求轉交給handler去處理,需要注意一下如下幾個連接參數的意義:
- SO_LINGER:若設置了SO_LINGER並確定了非零的超時間隔,則closesocket()調用阻塞進程,直到所剩數據發送完畢或超時。這種關閉稱為“優雅的”關 閉。請注意如果套接口置為非阻塞且SO_LINGER設為非零超時,則closesocket()調用將以WSAEWOULDBLOCK錯誤返回。若在一個流類套接口上設置了SO_DONTLINGER,則closesocket()調用立即返回。但是,如果可能,排隊的數據將在套接口關閉前發送。請注意,在這種情況下WINDOWS套接口實現將在 一段不確定的時間內保留套接口以及其他資源(TIME_WAIT),這對於想用所以套接口的應用程序來說有一定影響。默認此參數不打開
- TCP_NODELAY:是否打開Nagle,默認打開,使用Nagle算法是為了避免多次發送小的分組,而是累計到一定程度或者超過一定時間后才一起發送。對於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實現是org.apache.coyote.http11.Http11Processor,其主要處理一些HTTP協議性細節的東西,此處代碼不再列出,有興趣可以自行讀代碼。最終請求終於回到了我們的Adapter對象,一個請求處理完畢,功德圓滿。
來源:http://1632004.blog.163.com/blog/static/29991497201201912858468/