天天看點

套接字輸入緩沖裝置——InternalInputBuffer

網際網路的世界很複雜,資訊從一端傳向另一端過程也相當複雜,中間可能通過若幹個硬體,為了提高發送和接收效率,在發送端及接收端都将引入緩沖區,是以兩端的套接字都擁有各自的緩沖區,當然這種緩沖區的引入也帶來了不确定的延時,在發送端一般先将消息寫入緩沖區,直到緩沖區填滿才發送,而接收端則一次隻讀取最多不超過緩沖區大小的消息。

Tomcat在處理用戶端的請求時需要讀取用戶端的請求資料,它同樣需要一個緩沖區用于接收位元組流,即套接字輸入緩沖裝置,它主要的責任是提供一種緩沖模式從socket中讀取位元組流,提供填充緩沖區的方法,即将位元組讀到緩沖區buf,提供解析http協定請求行的方法,提供解析http協定請求頭的方法,按照解析的結果組裝請求對象Request。

套接字輸入緩沖裝置的工作原理并不會複雜,如下圖所示,InternalInputBuffer包含以下幾個變量:位元組數組buf、整型pos、整型lastValid、整型end。其中buf是用于存放緩沖的位元組流,它的大小由程式設定,tomcat中預設是設定為8 * 1024,即8k位元組;pos表示讀取指針,讀到哪個位置值即為多少;lastValid表示從作業系統底層讀取資料填充到buf中最後的位置;end表示緩沖區buf中http協定請求封包頭部結束的位置,同時也表示封包體的開始位置。

圖中從上往下看,最開始緩沖區buf是空的,将socket作業系統底層的若幹位元組流讀取到buf中,于是狀态如②所示,讀取到的位元組流将buf從頭往後進行填充,同時pos為0,lastValid為此次讀取後最後的位置值,接着第二次讀取作業系統底層若幹位元組流,每次讀取多少是不确定,位元組流應該接在②中lastValid指定的位置後面而非從頭開始,此時pos及lastValid根據實際情況被賦予新值,假如再讀取一次則最終狀态為⑤,多出了一個end變量,它的含義是http請求封包的請求行及請求頭結束的位置。

套接字輸入緩沖裝置——InternalInputBuffer

為了更好了解如何從底層讀取位元組流并進行解析,下面将給出簡化的處理過程,首先需要一個方法提供讀取位元組流,如下,其中inputStream代表套接字的輸入流,通過socket.getInputStream()擷取,其中read方法用于讀取位元組流,它表示從底層讀取最多(buf.length-lastValid)長度的位元組流,且把這些位元組流填入buf數組中,填充的起始位置為buf[pos]開始,nRead表示實際讀取到的位元組數。通過對上面這些變量的操作則可以準确操作緩沖裝置,成功填充傳回true。

publicclass InternalInputBuffer{

byte[] buf=newbyte[8*1024];

    int pos=0;

    int lastValid=0;

public booleanfill(){

int nRead = inputStream.read(buf,pos, buf.length - lastValid);            

if (nRead >0) {            

            lastValid = pos + nRead;           

        }

return (nRead> 0);

}

有了填充的方法往下需要一個解析封包的操作過程,受篇幅影響此處隻提供對請求行的方法及路徑的解析為例子說明,其他的解析按照類似操作即可。http協定請求封包的格式如圖,請求行一共有三個值需要解析出來:請求方法、請求url及協定版本,以空格間隔并以回車符換行符結尾。解析方法如下:

套接字輸入緩沖裝置——InternalInputBuffer

publicboolean parseRequestLine(){

        int start = 0;

        byte chr = 0;

        boolean space = false;

        while (!space) {

            if (pos >= lastValid)

                fill();

            if (buf[pos] == (byte) ' ') {

                space = true;

                byte[] methodB = new byte[pos -start];

                System.arraycopy(buf, start,methodB,0, pos - start);

                String method = newString(methodB);

                request.setMethod(method);

            }

            pos++;

        }

        while (space) {

                pos++;

            } else {

                space = false;

       start = pos;

                byte[] uriB = newbyte[pos-start];

                System.arraycopy(buf, start,uriB ,0, pos - start);

                String uri = new String(uriB);

                request.setUri(uri);

       return true;

    }

第一個while循環用于解析方法名,每次操作前必須判斷是否需要從底層讀取位元組流,當pos大于等于lastValid時即需要調用fill方法讀取,當位元組等于ASCII編碼的空格時就截取start到pos之間的位元組數組,它們便是方法名的位元組組成,轉成String對象後設定到request對象中;第二個while循環用于跳過方法名與uri之間所有的空格;第三個while循環用于解析uri,它的邏輯與前面方法名解析的邏輯差不多,解析到的uri最終也設定到request對象裡中。

至此,整個緩沖裝置的工作原理基本搞清楚了,一個完整的過程是從底層位元組流的讀取到對這些位元組流的解析并組裝成一個請求對象request友善程式後面使用,由于每次不能确切保證從底層讀取到的位元組流,于是通過對pos、lastValid變量進行控制以至于完成對位元組流的準确讀取接收。除此之外,輸入緩沖裝置還提供了解析請求頭部的方法,處理邏輯是按照http協定的規定對頭部解析,然後依次放入request對象中。需要額外說明的是,tomcat實際運作中并不會将請求行、請求頭等參數解析後就轉化為String類型設定到request,而是繼續使用ASCII碼存放這些值,因為對這些ASCII碼轉碼會導緻性能問題,它的思想是隻有到需要使用的時候再進行轉碼,很多參數沒使用到就不進行轉碼,以此提高處理性能。這方面詳細内容在Request章節有涉及。

<a target="_blank" href="https://item.jd.com/12185360.html">點選訂購作者《Tomcat核心設計剖析》</a>

繼續閱讀