Tomcat源碼學(xué)習(xí)之--請(qǐng)求數(shù)據(jù)傳輸

本文介紹基于tomcat7中默認(rèn)的BIO方式
以前說過tomcat通過socket傳輸 接口請(qǐng)求之路

發(fā)送數(shù)據(jù)

大部分的HTTP請(qǐng)求一般都是長(zhǎng)連接,可以從HTTP請(qǐng)求頭中看到


一般發(fā)送HTTP請(qǐng)求之前會(huì)建立一個(gè)socket連接匀奏。如果tomcat判斷需要對(duì)這個(gè)socket請(qǐng)求進(jìn)行關(guān)閉鞭衩,會(huì)在HTTP請(qǐng)求的響應(yīng)頭中寫入Connection:close,當(dāng)HTTP得到這樣的響應(yīng)就會(huì)關(guān)閉對(duì)應(yīng)的連接娃善。如果tomcat判斷這個(gè)連接可以繼續(xù)使用,保持長(zhǎng)連接论衍,響應(yīng)頭就不設(shè)置對(duì)應(yīng)的內(nèi)容,則會(huì)繼續(xù)使用Connection:keep-alive聚磺。
注意坯台,如果關(guān)閉的話是http發(fā)起關(guān)閉,而不是tomcat進(jìn)行關(guān)閉

長(zhǎng)連接表示請(qǐng)求完成后socket連接不關(guān)閉瘫寝,還可以繼續(xù)通過這個(gè)對(duì)應(yīng)的socket連接繼續(xù)發(fā)送請(qǐng)求蜒蕾。


長(zhǎng)連接請(qǐng)求和返回

發(fā)送http請(qǐng)求前需要建立一個(gè)socket連接稠炬,長(zhǎng)連接表示請(qǐng)求完成后socket連接不關(guān)閉,還可以通過這個(gè)socket連接繼續(xù)發(fā)送請(qǐng)求咪啡。如果tomcat如果這個(gè)socket請(qǐng)求需要關(guān)閉首启,那么http響應(yīng)頭中Connection:close。這樣接收完響應(yīng)之后就會(huì)關(guān)閉socke連接撤摸,如果保持長(zhǎng)連接毅桃,http響應(yīng)頭就不設(shè)置。

接收數(shù)據(jù)

  1. Tomcat通過Processor(所有協(xié)議處理器的通用接口)處理請(qǐng)求准夷。


  2. Processor內(nèi)部會(huì)設(shè)置Endpoint钥飞,并通過Endpoint的內(nèi)部類Acceptor接收socket連接。
  3. 客戶端發(fā)送數(shù)據(jù)衫嵌,在服務(wù)端的操作系統(tǒng)中有個(gè)RecvBuf(緩沖區(qū))读宙,操作系統(tǒng)會(huì)將數(shù)據(jù)先放在recvBufferbyte中,如果recvbuf中填滿了楔绞,就無(wú)法發(fā)送數(shù)據(jù)了结闸。每個(gè)socket對(duì)應(yīng)一個(gè)緩沖區(qū)。當(dāng)客戶端寫socket的發(fā)送數(shù)據(jù)是墓律,也會(huì)放在緩沖區(qū)sendbuf中膀估。

參考如下源碼內(nèi)容:

  • AbstractHttp11Processor類中的processSocketWapper方法
  • JIOEndpoint類中有個(gè)Acceptor方法幔亥,會(huì)通過BIO的方式接受socket
  • Socket=serverSocketFactory.accept
  • setSocketOption設(shè)置socket參數(shù)的方法中有個(gè)soTimeout參數(shù)耻讽,這個(gè)參數(shù)是指當(dāng)socke從recvbuf中read數(shù)據(jù)時(shí),如果recvbuf中數(shù)據(jù)為空帕棉,則read阻塞针肥,具體阻塞時(shí)間就是這個(gè)參數(shù)指定的時(shí)間。
  1. 第一次從socket中獲取到數(shù)據(jù)寫入InputBuffer中香伴,然后進(jìn)行解析對(duì)應(yīng)的請(qǐng)求行(請(qǐng)求頭)慰枕,請(qǐng)求方法,請(qǐng)求協(xié)議等放入到Request中即纲,并且設(shè)置一些參數(shù)具帮。
    處理socket
  • BIO的方式,每接收到一個(gè)socket就將其交給一個(gè)線程低斋,在tomcat的BIO里是每個(gè)請(qǐng)求都有一個(gè)對(duì)應(yīng)的線程進(jìn)行處理蜂厅,當(dāng)socket關(guān)閉后,這個(gè)線程也會(huì)被釋放膊畴。
  • NIO則是一個(gè)線程處理多個(gè)請(qǐng)求掘猿。

參考如下源碼內(nèi)容:

  • JIoEndpoint類中的prcocessSocket方法的getExecutor.execute
  • 在BIO的方式中最大請(qǐng)求數(shù)maxConnection和最大線程數(shù)MaxThread實(shí)際是一致的
  • 在調(diào)用線程處理時(shí)根據(jù)run方法的process調(diào)用中的的內(nèi)容,找到AbstarctProtocol中的process方法唇跨,方法會(huì)發(fā)現(xiàn)首先要調(diào)用createProcessor創(chuàng)建一個(gè)處理稠通,根據(jù)不同的實(shí)現(xiàn)類里面設(shè)置了不同的屬性衬衬,這個(gè)屬性也可以通過<Connector>標(biāo)簽來設(shè)置。maxKeepAliveRequests屬性改橘,表示這個(gè)長(zhǎng)連接上能夠處理的最大活動(dòng)數(shù)(http請(qǐng)求)默認(rèn)100
  • 創(chuàng)建處理器完成后滋尉,調(diào)用對(duì)應(yīng)process方法,解析http請(qǐng)求中的數(shù)據(jù)飞主。
  • 如果maxKeepAliveRequests==1兼砖,那么處理完后連接就會(huì)被關(guān)閉,意味著對(duì)應(yīng)的設(shè)置keepalive=false
  • 對(duì)應(yīng)的判斷既棺,如讽挟,當(dāng)前活躍的線程數(shù)占線程池最大線程數(shù)的比例大于 75%,那么則關(guān)閉KeepAlive丸冕,不再支持長(zhǎng)連接耽梅,不支持長(zhǎng)連接并不表示不支持連接處理,意味著75%之后的ssocket連接將會(huì)被當(dāng)成短鏈接處理胖烛。但是當(dāng)超過100%的socket連接數(shù)時(shí)眼姐,將不再處理后續(xù)的socke連接。
  • getInputBuffer().parseRequest和getInputBuffer().parseHeaders()讀取請(qǐng)求體和請(qǐng)求頭
  1. 調(diào)用adapter進(jìn)行服務(wù)處理佩番,調(diào)用fill等一系列方法众旗。

fill方法是將操作系統(tǒng)中的revfbuf讀取到tomcat的緩沖區(qū)buf中(默認(rèn)8kb)

  1. 處理過程中會(huì)將對(duì)應(yīng)的內(nèi)容放入響應(yīng)response中,返回給客戶端趟畏。同樣的也是先放在緩沖區(qū)贡歧,最后將緩沖區(qū)的數(shù)據(jù)返回。
  2. 最后判斷數(shù)據(jù)是否響應(yīng)完成赋秀,把剩余的數(shù)據(jù)清除完利朵,獲取下個(gè)請(qǐng)求。
public SocketState process(SocketWrapper<S> socketWrapper)
    throws IOException {
    RequestInfo rp = request.getRequestProcessor();
    // 設(shè)置請(qǐng)求狀態(tài)為解析狀態(tài)
    rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);   
    // 設(shè)置IO
    setSocketWrapper(socketWrapper);
    //設(shè)置輸入猎莲、輸出緩沖區(qū)
    //將socket的InputStream與InternalInputBuffer進(jìn)行綁定(緩沖區(qū)內(nèi)容)
    getInputBuffer().init(socketWrapper, endpoint);
    // 將socket的OutputStream與InternalOutputBuffer進(jìn)行綁定
    getOutputBuffer().init(socketWrapper, endpoint);
      // 長(zhǎng)連接等一系列標(biāo)志
    keepAlive = true;
    // NioEndpoint返回true, BIO返回false
    if (endpoint.getUsePolling()) {
        keptAlive = false;
    } else {
        keptAlive = socketWrapper.isKeptAlive();
    }
    // 如果當(dāng)前活躍的線程數(shù)占線程池最大線程數(shù)的比例大于75%(默認(rèn)值)绍弟,那么則關(guān)閉KeepAlive,不再支持長(zhǎng)連接
    if (disableKeepAlive()) {
        socketWrapper.setKeepAliveLeft(0);
    }
    // keepAlive的值會(huì)從請(qǐng)求中讀取著洼,默認(rèn)為true
    while () {
        // keepAlive如果為true,接下來需要從socket中不停的獲取http請(qǐng)求
        // 解析請(qǐng)求頭
        try {
            // 第一次從socket中讀取數(shù)據(jù)樟遣,并設(shè)置socket的讀取數(shù)據(jù)的超時(shí)時(shí)間
            // 對(duì)于BIO,一個(gè)socket連接建立好后身笤,不一定馬上就被Tomcat處理了豹悬,其中需要線程池的調(diào)度,所以這段等待的時(shí)間要算在socket讀取數(shù)據(jù)的時(shí)間內(nèi)展鸡;而對(duì)于NIO而言屿衅,沒有阻塞
            //第一次連接的時(shí)候用,超時(shí)時(shí)間=真實(shí)的時(shí)間-socket建立的時(shí)間
            setRequestLineReadTimeout();
            // 解析請(qǐng)求行
            if (!getInputBuffer().parseRequestLine(keptAlive)) {               
            }
            if (endpoint.isPaused()) {
               //如果Endpoint被暫停了莹弊,則返回503
            } else {
                keptAlive = true;
                // 每次處理一個(gè)請(qǐng)求就重新獲取一下請(qǐng)求頭和cookies的最大限制
                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());//默認(rèn)100
                request.getCookies().setLimit(getMaxCookieCount());//默認(rèn)200                            }
        } 
        //如果最大的保持連接請(qǐng)求數(shù)量==1涤久,表示只允許一次請(qǐng)求
        if (maxKeepAliveRequests == 1) {
            keepAlive = false;//長(zhǎng)連接參數(shù)直接設(shè)為false
        } else if (maxKeepAliveRequests > 0 &&
                socketWrapper.decrementKeepAlive() <= 0) {
            // 如果已經(jīng)達(dá)到了keepAlive的最大限制涡尘,也設(shè)置為false,則不會(huì)繼續(xù)從socket中獲取Http請(qǐng)求了
            keepAlive = false;
        }
        //在適配器開始處理請(qǐng)求
        if (!getErrorState().isError()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE); // 設(shè)置請(qǐng)求狀態(tài)為服務(wù)狀態(tài)响迂,表示正在處理請(qǐng)求
                adapter.service(request, response); // 處理請(qǐng)求
            } 
        }
        // 完成請(qǐng)求處理
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);  // 設(shè)置請(qǐng)求的狀態(tài)為處理請(qǐng)求結(jié)束
        if (!isAsync() && !comet) {
            // 當(dāng)前http請(qǐng)求已經(jīng)處理完了考抄,做一些收尾工作
            endRequest();
        }
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT); // 請(qǐng)求狀態(tài)為輸出結(jié)束
       if (!isAsync() && !comet || getErrorState().isError()) {
            if (getErrorState().isIoAllowed()) {
                // 準(zhǔn)備處理下一個(gè)請(qǐng)求
                getInputBuffer().nextRequest();
                getOutputBuffer().nextRequest();
            }
        }      
        rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
        // 如果處理完當(dāng)前這個(gè)Http請(qǐng)求之后,發(fā)現(xiàn)socket里沒有下一個(gè)請(qǐng)求了,那么就退出當(dāng)前循環(huán)
        // 如果是keepalive蔗彤,就不會(huì)關(guān)閉socket, 如果是close就會(huì)關(guān)閉socket
        // 對(duì)于keepalive的情況川梅,因?yàn)槭且粋€(gè)線程處理一個(gè)socket,當(dāng)退出這個(gè)while后,當(dāng)前線程就會(huì)介紹然遏,
        // 當(dāng)時(shí)對(duì)于socket來說贫途,它仍然要繼續(xù)介紹連接,所以又會(huì)新開一個(gè)線程繼續(xù)來處理這個(gè)socket
        if (breakKeepAliveLoop(socketWrapper)) {
            break;
        }
    }
    rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末待侵,一起剝皮案震驚了整個(gè)濱河市丢早,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秧倾,老刑警劉巖怨酝,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異那先,居然都是意外死亡农猬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門售淡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斤葱,“玉大人,你說我怎么就攤上這事勋又∪タ郏” “怎么了满败?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肌稻。 經(jīng)常有香客問我惯驼,道長(zhǎng)蹲嚣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任祟牲,我火速辦了婚禮隙畜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘说贝。我一直安慰自己议惰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布乡恕。 她就那樣靜靜地躺著言询,像睡著了一般俯萎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上运杭,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天夫啊,我揣著相機(jī)與錄音,去河邊找鬼辆憔。 笑死撇眯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虱咧。 我是一名探鬼主播熊榛,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼腕巡!你這毒婦竟也來了来候?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逸雹,失蹤者是張志新(化名)和其女友劉穎营搅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梆砸,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡转质,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帖世。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片休蟹。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖日矫,靈堂內(nèi)的尸體忽然破棺而出赂弓,到底是詐尸還是另有隱情,我是刑警寧澤哪轿,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布盈魁,位于F島的核電站,受9級(jí)特大地震影響窃诉,放射性物質(zhì)發(fā)生泄漏杨耙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一飘痛、第九天 我趴在偏房一處隱蔽的房頂上張望珊膜。 院中可真熱鬧,春花似錦宣脉、人聲如沸车柠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竹祷。三九已至介蛉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溶褪,已是汗流浹背币旧。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猿妈,地道東北人吹菱。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像彭则,于是被迫代替她去往敵國(guó)和親鳍刷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容