tomcat4默認連接器簡要分析

1、HTTP 1.1新特性

1)持久連接:connection: keep-alive

2)塊編碼:使用長連接后,發(fā)送方大部分時候無法計算出要發(fā)送的內(nèi)容長度,也不能等所有資源都準備好了再發(fā)送,HTTP 1.1 使用transfer-encoding header來處理這個問題。transfer-encoding表示有多少以塊形式的字節(jié)流將會被發(fā)送給接收方,對于每一個塊數(shù)據(jù),長度(16進制)+ CR/LF + 數(shù)據(jù)將會被發(fā)送,整個事物以0\r\n結(jié)束。例如:I'm as helpless as a kitten up a tree.這個數(shù)據(jù)如果以3個塊發(fā)送的話,其發(fā)送格式為:

1D\r\n
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n

3) Expect: 100-continue Header 使用,當要發(fā)送長內(nèi)容請求的時候,客戶端在無法保證服務(wù)器一定會接收的時候,可以先發(fā)送這個header到服務(wù)器詢問是否允許客戶端的長內(nèi)容請求,如果允許,服務(wù)端會返回HTTP/1.1 100 Continue + CRLF + CRLF給客戶端。

2、連接器的職責:

1)接收客戶端的請求,解析HTTP協(xié)議:請求行(GET /api/xxx HTTP/1.1 ...)解析,header解析,數(shù)據(jù)解析
2)構(gòu)建Request對象
3)構(gòu)建Response對象

3、連接器處理流程

image.png
image.png
image.png

整個類圖分為三塊,類圖1為連接器和容器、連接器和處理器之間的關(guān)系;類圖2為Request結(jié)構(gòu)圖,類圖3為Response結(jié)構(gòu)圖。

3、1 HttpConnector啟動階段

1)通過調(diào)用initialize()方法和open()方法,創(chuàng)建了一個ServerSocket對象

2)調(diào)用start()方法,一是啟動接收客戶端請求的線程,二是初始化HttpProcessor對象池,該對象池為Stack,這里主要看一下HttpProcessor的創(chuàng)建和啟動。

private HttpProcessor newProcessor() {
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        if (processor instanceof Lifecycle) {
            try {
                /**
                 * HttpProcessor 本身是一個Runnable和Lifecycle,在這里對其進行了啟動操作,
                 * 其實就是啟動了一個線程,來執(zhí)行HttpProcessor這個Runnable
                 */
                ((Lifecycle) processor).start();
            } catch (LifecycleException e) {
                log("newProcessor", e);
                return (null);
            }
        }
        created.addElement(processor);
        return (processor);
    }
3、2 HttpConnector對請求處理過程
public void run() {
    socket = serverSocket.accept();
    HttpProcessor processor = createProcessor();
    processor.assign(socket);
}

run()主體邏輯就上面三行代碼,在接收到請求后,先通過createProcessor方法獲取一個HttpProcessor對象,createProcessor獲取HttpProcessor對象的邏輯比較簡單,直接看代碼就行。

private HttpProcessor createProcessor() {
        synchronized (processors) {
            if (processors.size() > 0) {
// 對象池中還有對象,直接從池子里面獲取
                return ((HttpProcessor) processors.pop());
            }
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
// 池子里面沒有了,只要沒有超過設(shè)置的最大容量,就創(chuàng)建一個對象
                return (newProcessor());
            } else {
                if (maxProcessors < 0) {
// 如果最大容量設(shè)置為小于0,則直接創(chuàng)建一個對象
                    return (newProcessor());
                } else {
                    return (null);
                }
            }
        }
    }

獲取到HttpProcessor之后,調(diào)用其assign方法。

3、3 HttpProcessor啟動過程

通過上面知道,HttpProcessor是在HttpConnector的newProcessor()中進行創(chuàng)建和啟動的,在start()中,其主要工作是啟動了處理線程

public void start() throws LifecycleException {
        if (started)
            throw new LifecycleException
                (sm.getString("httpProcessor.alreadyStarted"));
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;
// 啟動線程,Runnable為本身
        threadStart();
    }

現(xiàn)在看一下這個線程的主要工作是啥:

 public void run() {
        // Process requests until we receive a shutdown signal
        while (!stopped) {
            // Wait for the next socket to be assigned
            // 1、調(diào)用await()方法獲取一個socket
            Socket socket = await();
            if (socket == null)
                continue;
            // Process the request from this socket
            try {
            // 2、對socket進行處理
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }
            // Finish up this request
            // 3、對象回收
            connector.recycle(this);
        }
        // Tell threadStop() we have shut ourselves down successfully
        synchronized (threadSync) {
            threadSync.notifyAll();
        }
    }

可以看到,其工作主要是:從await()方法獲取socket,交給process()方法處理,然后HttpConnector對該對象進行回收再利用。

看一下socket是如何獲取的:

private synchronized Socket await() {
        // Wait for the Connector to provide a new Socket
        while (!available) {
            try {
                /**
                 * 進入等待狀態(tài),直到HTTPConnector線程調(diào)用了notifyAll().
                 * 這個喚醒操作在assign()中完成。
                 */
                wait();//
            } catch (InterruptedException e) {
            }
        }
        // Notify the Connector that we have received this Socket
        Socket socket = this.socket;
        available = false;
        /**
         * 這里是喚醒assign()中的wait(),讓其可以繼工作了。
         */
        notifyAll();
        return socket;
    }

await首先進入一個等待狀態(tài),只有被其他線程喚醒了,才會進行下一步的工作,也就是返回socket,那么這個等待由誰喚醒呢?在HTTPConnector的run()方法中可以知道,socket來源于HTTPConnector,然后其把這個socket傳給了HttpProcessor的assign()方法:

synchronized void assign(Socket socket) {
        // Wait for the Processor to get the previous Socket
        while (available) {
            try {
// 讓當前線程進入等待狀態(tài),assign方法由HTTPConnector所在的線程
// 調(diào)用,wait是在HttpProcessor對象中起調(diào)的,所以這里是
// HttpProcessor讓HTTPConnector所在的線程進行等待
                wait();
            } catch (InterruptedException e) {
            }
        }
        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
// 喚醒其他線程中的所有wait()方法,這里的調(diào)用者是HttpProcessor,
//也就是喚醒HttpProcessor線程中所有的wait()方法
        notifyAll();
    }

assign()方法接收來自于HTTPConnector的socket,然后賦值在HttpProcessor的全局變量this.socket上,在await()方法中就能獲取到這個socket了,因為assign()工作在HTTPConnector所屬的線程,而await()工作在HttpProcessor所屬的線程中,兩者之間通過wait()和notifyAll()來互相通信。

這里有個疑問,不知道assign()里面為什么要wait(),await()里面為什么要notifyAll(),根據(jù)設(shè)計,HttpProcessor在當前socket沒有處理完的時候,其不會被回收到對象池中,也就根本沒有機會去處理下一個socket,但是這里卻這樣設(shè)計了,而且看await方法返回的socket也不是全局變量,而是用了一個局部變量來存儲然后返回的是這個局部變量。官方的解釋是說全局的變量用來放置下一個到來的socket,以防當前socket在沒有完全處理完成而下一個socket又到來的情況,但是這種情況是怎么出現(xiàn)的,不解。

3、4 HttpProcessor解析HTTP協(xié)議過程簡析

tomcat默認連接器對socket的處理邏輯,主要在HttpProcessor類中的process()方法,其主要工作是解析連接,解析請求行,解析頭部,這里它沒有對參數(shù)進行解析,參數(shù)是在需要的時候才會進行解析,主要是不過多的占用CPU時間,使CPU有更多的時間來處理客戶的請求,提升并發(fā)量。

private void process(Socket socket) {
        // 用來記錄處理過程是否正確
        boolean ok = true;
        //  用來標記Response接口中的 finishResponse 方法是否應該被調(diào)用
        boolean finishResponse = true;
        SocketInputStream input = null;
        OutputStream output = null;// 輸出流
        // Construct and initialize the objects we will need
        try {
            // 包裝了一個InputStream,用來解析請求行和頭部信息
            input = new SocketInputStream(socket.getInputStream(),
                                          connector.getBufferSize());
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }

        // 是否持久連接,HTTP/1.1才設(shè)置為true,見parseRequest()
        keepAlive = true;
        while (!stopped && ok && keepAlive) {
            finishResponse = true;
            try {// request/response基本配置,輸入輸出流
                request.setStream(input);
                request.setResponse(response);
                output = socket.getOutputStream();
                response.setStream(output);
                response.setRequest(request);
                ((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
            } catch (Exception e) {
                log("process.create", e);
                ok = false;
            }

            // Parse the incoming request
                if (ok) {
                    // 解析服務(wù)器地址和端口號
                    parseConnection(socket);
                    // 這個主要是用來解析請求行: GET /api/xxx?a=11&b=22.... HTTP/1.1
                    parseRequest(input, output);
                    // Header解析
                    if (!request.getRequest().getProtocol().startsWith("HTTP/0")) {
                        parseHeaders(input);
                    }
                    // 如果是HTTP/1.1,回復"HTTP/1.1 100 Continue\r\n\r\n"
                    if (http11) {
                        ackRequest(output);
                        // If the protocol is HTTP/1.1, chunking is allowed.
                        if (connector.isChunkingAllowed()) {
                            response.setAllowChunking(true);
                        }
                    }
                }
           
            // Ask our Container to process this request
            try {
                if (ok) {
                    // 交給Container來處理請求
                    connector.getContainer().invoke(request, response);
                }
            } 

            // Finish up the handling of the request
            if (finishResponse) {
                try {
                    response.finishResponse();
                } 
                try {
                    request.finishRequest();
                } 
                try {
                    if (output != null)
                        output.flush();
                } catch (IOException e) {
                    ok = false;
                }
            }

            // We have to check if the connection closure has been requested
            // by the application or the response stream (in case of HTTP/1.0
            // and keep-alive).
            if ( "close".equals(response.getHeader("Connection")) ) {
                keepAlive = false;
            }
            // End of request processing
            status = Constants.PROCESSOR_IDLE;
            // Recycling the request and the response objects
            request.recycle();
            response.recycle();
        }
        try {
            shutdownInput(input);
            socket.close();
        }
        socket = null;
    }

整體流程上還是比較清晰的,具體怎么解析的這里就不描述了,在處理完成后,是交給了容器的invoke方法進行處理的,至于容器是如何處理這個請求的,這個只有在分析完容器后才知曉了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,282評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,684評論 1 32
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,873評論 11 349
  • Java繼承關(guān)系初始化順序 父類的靜態(tài)變量-->父類的靜態(tài)代碼塊-->子類的靜態(tài)變量-->子類的靜態(tài)代碼快-->父...
    第六象限閱讀 2,261評論 0 9
  • 今天的拍球練習,發(fā)現(xiàn)好久不練退步了不少。而且和朋友聊天發(fā)現(xiàn)下面要把大運動訓練重視起來,最好能成為至少是每周的必練項...
    Hisi閱讀 273評論 0 0

友情鏈接更多精彩內(nèi)容