tomcat4默認連接器簡要分析

1、HTTP 1.1新特性

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

2)塊編碼:使用長連接后嫌褪,發(fā)送方大部分時候無法計算出要發(fā)送的內容長度,也不能等所有資源都準備好了再發(fā)送,HTTP 1.1 使用transfer-encoding header來處理這個問題强窖。transfer-encoding表示有多少以塊形式的字節(jié)流將會被發(fā)送給接收方泪姨,對于每一個塊數據游沿,長度(16進制)+ CR/LF + 數據將會被發(fā)送,整個事物以0\r\n結束肮砾。例如:I'm as helpless as a kitten up a tree.這個數據如果以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ā)送長內容請求的時候仗处,客戶端在無法保證服務器一定會接收的時候眯勾,可以先發(fā)送這個header到服務器詢問是否允許客戶端的長內容請求,如果允許婆誓,服務端會返回HTTP/1.1 100 Continue + CRLF + CRLF給客戶端吃环。

2、連接器的職責:

1)接收客戶端的請求洋幻,解析HTTP協議:請求行(GET /api/xxx HTTP/1.1 ...)解析郁轻,header解析,數據解析
2)構建Request對象
3)構建Response對象

3文留、連接器處理流程

image.png
image.png
image.png

整個類圖分為三塊范咨,類圖1為連接器和容器故觅、連接器和處理器之間的關系;類圖2為Request結構圖渠啊,類圖3為Response結構圖输吏。

3、1 HttpConnector啟動階段

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

2)調用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)) {
// 池子里面沒有了炎咖,只要沒有超過設置的最大容量赃泡,就創(chuàng)建一個對象
                return (newProcessor());
            } else {
                if (maxProcessors < 0) {
// 如果最大容量設置為小于0,則直接創(chuàng)建一個對象
                    return (newProcessor());
                } else {
                    return (null);
                }
            }
        }
    }

獲取到HttpProcessor之后乘盼,調用其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();
    }

現在看一下這個線程的主要工作是啥:

 public void run() {
        // Process requests until we receive a shutdown signal
        while (!stopped) {
            // Wait for the next socket to be assigned
            // 1勺阐、調用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線程調用了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所在的線程
// 調用默刚,wait是在HttpProcessor對象中起調的甥郑,所以這里是
// HttpProcessor讓HTTPConnector所在的線程進行等待
                wait();
            } catch (InterruptedException e) {
            }
        }
        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
// 喚醒其他線程中的所有wait()方法,這里的調用者是HttpProcessor荤西,
//也就是喚醒HttpProcessor線程中所有的wait()方法
        notifyAll();
    }

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

這里有個疑問赂蕴,不知道assign()里面為什么要wait(),await()里面為什么要notifyAll(),根據設計柳弄,HttpProcessor在當前socket沒有處理完的時候舶胀,其不會被回收到對象池中,也就根本沒有機會去處理下一個socket碧注,但是這里卻這樣設計了嚣伐,而且看await方法返回的socket也不是全局變量,而是用了一個局部變量來存儲然后返回的是這個局部變量萍丐。官方的解釋是說全局的變量用來放置下一個到來的socket轩端,以防當前socket在沒有完全處理完成而下一個socket又到來的情況,但是這種情況是怎么出現的逝变,不解基茵。

3、4 HttpProcessor解析HTTP協議過程簡析

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

private void process(Socket socket) {
        // 用來記錄處理過程是否正確
        boolean ok = true;
        //  用來標記Response接口中的 finishResponse 方法是否應該被調用
        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才設置為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) {
                    // 解析服務器地址和端口號
                    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方法進行處理的,至于容器是如何處理這個請求的强饮,這個只有在分析完容器后才知曉了由桌。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市邮丰,隨后出現的幾起案子行您,更是在濱河造成了極大的恐慌,老刑警劉巖剪廉,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娃循,死亡現場離奇詭異,居然都是意外死亡斗蒋,警方通過查閱死者的電腦和手機捌斧,發(fā)現死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泉沾,“玉大人捞蚂,你說我怎么就攤上這事□尉浚” “怎么了姓迅?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俊马。 經常有香客問我丁存,道長,這世上最難降的妖魔是什么柴我? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任解寝,我火速辦了婚禮,結果婚禮上屯换,老公的妹妹穿的比我還像新娘编丘。我一直安慰自己与学,他們只是感情好柴淘,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布幌衣。 她就那樣靜靜地躺著剑令,像睡著了一般辆童。 火紅的嫁衣襯著肌膚如雪玻褪。 梳的紋絲不亂的頭發(fā)上亏推,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天呻顽,我揣著相機與錄音茧痕,去河邊找鬼敞斋。 笑死截汪,一個胖子當著我的面吹牛,可吹牛的內容都是我干的植捎。 我是一名探鬼主播衙解,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼焰枢!你這毒婦竟也來了蚓峦?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤济锄,失蹤者是張志新(化名)和其女友劉穎暑椰,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體荐绝,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡一汽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了低滩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片召夹。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖委造,靈堂內的尸體忽然破棺而出戳鹅,到底是詐尸還是另有隱情均驶,我是刑警寧澤昏兆,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站妇穴,受9級特大地震影響爬虱,放射性物質發(fā)生泄漏。R本人自食惡果不足惜腾它,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一跑筝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞒滴,春花似錦曲梗、人聲如沸赞警。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愧旦。三九已至,卻和暖如春定罢,著一層夾襖步出監(jiān)牢的瞬間笤虫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工祖凫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留琼蚯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓惠况,卻偏偏與公主長得像遭庶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子稠屠,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容

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