小型web服務器thttpd的學習總結(下)

一、主函數模塊分析

對於主函數而言,歸納來講主要作了三點內容,也就是初始化系統,進行系統大循環,退出系統。下面主要簡單闡述下在這三個部分,又作了哪些工做呢。html

初始化系統

  1. 拿出程序的名字(argv[0])用來做爲參數打開那個log(syslog)web

  2. 解析命令行的參數(parse_args),初始化內部的參數變量api

  3. 檢查當前主機名(addr) 沒有的話利用gethostbyname從hostname中獲取數組

  4. 檢查當前要使用的主機端口(port)緩存

  5. 讀取Throttle file(門限文件,這裏省略)安全

  6. 檢查logfile的值,有的話就建立一個logfp咯服務器

  7. 得到系統用戶的相關信息(getpwnam),使用的系統用戶爲nobody(安全),記錄下uid,gid值cookie

  8. 切換程序的工做空間爲參數中的dir值數據結構

  9. 得到當前工做目錄(保證以'/'結尾)app

  10. 調用daemon函數進入後臺工做

  11. 查看pidfile,若是pidfile不爲空則打開該文件,寫入pid值

  12. 根據參數選擇是否chroot,(chroot的緣由見這個連接

  13. 設置信號處理函數signal(處理SIGTERM SIGINT SIGPIPE SIGHUP SIGUSR1)

  14. 初始化http處理模塊(調用該模塊init函數)

  15. 設置一個occasional timer用於時不時的清除定時器模塊和mmc模塊的無用內存,若是有須要的話,設置一個status timer,用於記錄狀態

  16. 爲了安全,放棄root權限,變成nobody(使用setgroups和setgid,setuid函數族)

  17. 利用fdwatch包裝的api,得到最多能夠複用的fd數

  18. 建立一個鏈接池(數組)每一個鏈接的數據結構以下,並完成初始化操做。

    typedef struct {
    int conn_state; //鏈接狀態
    httpd_conn* hc; //用戶信息
    int tnums[MAXTHROTTLENUMS]; /* throttle indexes /
    int numtnums; //
    long limit; //
    time_t started_at; //起始時間
    Timer
    idle_read_timer; //空閒讀取定時器
    Timer* idle_send_timer; //空閒發送定時器
    Timer* wakeup_timer; //甦醒定時器
    Timer* linger_timer; //
    long wouldblock_delay; //
    off_t bytes; //****
    off_t bytes_sent; //發送的數據
    off_t bytes_to_send; //還須要發送的數據
    } connecttab;

系統大循環

  1. 得到當前的時間,開始大循環

  2. 循環的條件時 terminate != 0 || numconnects > 0

  3. 循環的內容是:

    • 若是fdwatch_recompute標誌是1,清除原來的fdwatch內部變量,從新設置哪些文件須要被監控讀和寫。若是某鏈接狀態是reading或者lingering則觀測該鏈接的讀狀態;而若是某個鏈接的狀態是sending,則觀測該鏈接的寫狀態,(記得還要觀測服務器的監聽fd讀狀態)。
    • 而後就開始fdwatch全部描述符了,時間參數是下一個定時器觸發的時間,從而在定時器觸發前一直監聽。
    • 接着開始處理觀測結果,若是沒有fd準備好,那就開始運行定時器。
    • 不然,根據是新的鏈接仍是當前鏈接池中的鏈接以及其事件進行相應的處理。

新來的鏈接處理

  • 首先,保證鏈接數不大於最大的鏈接數

  • 接着,找到鏈接池中的最靠前的free鏈接,新建一個用戶數據結構,代表爲未初始化;

  • 調用http模塊的httpd_get_conn函數,初始化該用戶信息,而後填充些鏈接信息到數據結構中,開啓read定時器;

  • 設置該鏈接爲非阻塞的鏈接;

fd可讀時的處理

  • 首先,看是否有更多的空間來存取用戶的請求數據,若是沒有的話,給read_buf增長空間,每次1000字節,5000封頂;

  • 而後,從鏈接中讀取數據;

  • 判斷當前讀入的數據是否能構成一個合理的http request;

  • 若是能夠的話,進行http解析請求;

  • 設置須要給用戶放回的數據;

  • 設置鏈接的狀態爲SENDING,中止該鏈接的讀定時,開啓該用戶的寫定時;

fd可寫時的處理

  • 查看response中是否有值,若是沒有,則直接開始寫文件,該文件已經被映射到內存,直接從hc中的file_address中讀便可。而若是有值的話,則寫response中和file_address中的數據;
  • 若是沒有寫成功的話,設置鏈接狀態爲pause,並設置wakeup定時器,過會兒從新發送;
  • 從新設置定時器,根據發送數據的狀況將responlen清零,並設置bytes_sent中的值,按狀況清除鏈接仍是直接返回。

fd須要linger時的處理

若是有數據直接讀取數據扔掉

退出系統

  1. 清除已分配的內存
  2. 關閉系統日誌
  3. 退出

二、httpd模塊分析

在httpd模塊中,定義了兩個核心數據結構,服務器數據(http_server)和用戶鏈接數據(httpd_conn)。

服務器的數據結構的定義分別以下:

/* A server. */
typedef struct {
    char* hostname;                  //主機名(ex:localhost)
    struct in_addr host_addr;         //主機地址
    int port;                        //端口號
    char* cgi_pattern;                //cgi樣式
    char* cwd;                        //當前工做路徑
    int listen_fd;                    //監聽套接字
    FILE* logfp;                     //log文件描述符
    int no_symlinks;                 //有無符號鏈接標誌
    int vhost;                       //虛擬主機標誌
} httpd_server;

下面是鏈接的數據結構:

/* A connection. */
typedef struct {
        int initialized;                 //初始化標誌
        httpd_server* hs;         //服務器結構地址
          struct in_addr client_addr;       //客戶端地址      
        char* read_buf;                  //讀緩存
        int read_size, read_idx, checked_idx; //緩存標誌位
        int checked_state;               //檢測狀態標誌
        int method;                      //請求方法標誌
        int status;                      //當前鏈接狀態
        off_t bytes;
        char* encodedurl;                 //encode後的url
        char* decodedurl;                //decode後的url
        char* protocol;                  //http協議類型
        char* origfilename;               //原來的文件名
        char* expnfilename;              //擴展後的文件名
        char* encodings;                  
        char* pathinfo;
        char* query;
        char* referer;
        char* useragent;
        char* accept;
        char* accepte;
        char* cookie;
        char* contenttype;
        char* reqhost;
        char* hdrhost;
        char* authorization;
        char* remoteuser;
        char* response;                 //發送緩存
        int maxdecodedurl, maxorigfilename, maxexpnfilename, maxencodings,maxpathinfo, maxquery, maxaccept, maxaccepte, maxreqhost,maxremoteuser, maxresponse;
#ifdef TILDE_MAP_2
        char* altdir;
        int maxaltdir;
#endif                      
        int responselen;
        time_t if_modified_since, range_if;
        off_t contentlength;
        char* type;     /* not malloc()ed */
        char* hostname; /* not malloc()ed */
        int mime_flag;
        int one_one;    /* HTTP/1.1 or better */
        int got_range;
        int tildemapped;    /* this connection got tilde-mapped */
        off_t init_byte_loc, end_byte_loc;
        int keep_alive;
        int should_linger;
        struct stat sb;
        int conn_fd;
        char* file_address;
} httpd_conn;

該模塊提供的函數接口有:

//初始化http server數據結構
extern httpd_server* httpd_initialize(
char* hostname, u_int addr, int port, char* cgi_pattern, char* cwd,
FILE* logfp, int no_symlinks, int vhost );

//改變http server結構中的logfp
extern void httpd_set_logfp( httpd_server* hs, FILE* logfp );

//清除http server結構
extern void httpd_terminate( httpd_server* hs );

//當有一個新鏈接來臨時,接收這個鏈接,並將該鏈接http client初始化
extern int httpd_get_conn( httpd_server* hs, httpd_conn* hc );

//根據鏈接hc的read_buf中的內容,判斷當前接收的數據是不是一個完成的http請求,並返回對應結果
extern int httpd_got_request( httpd_conn* hc );

//解析上述的http請求,並把解析後的值放入hc對應的數據單元中
extern int httpd_parse_request( httpd_conn* hc );

//準備須要向客戶端發送的數據
extern int httpd_start_request( httpd_conn* hc );

//把hc中response中的內容寫給用戶
extern void httpd_write_response( httpd_conn* hc );

//關閉一個鏈接並釋放鏈接的空間
extern void httpd_close_conn( httpd_conn* hc, struct timeval* nowP );

//釋放hc中全部的空間
extern void httpd_destroy_conn( httpd_conn* hc );


//向客戶端發送一個錯誤信息
extern void httpd_send_err(
httpd_conn* hc, int status, char* title, char* form, char* arg );

//根據method號找到method內容
extern char* httpd_method_str( int method );

//從新分配一段string
extern void httpd_realloc_str( char** strP, int* maxsizeP, int size );

其中,系統操做這個httpd模塊則能夠分爲以下幾部進行理解。

  • 使用對應的接口進行httpd模塊的初始化,對應http server的初始化採用httpd_initialize接口,初始化好了以後就在對應的端口上進行監聽套接字;

  • 當監聽的套接字可讀以後,就可使用httpd_get_conn函數,accept該用戶,並開闢一個用戶的httpd_conn結構,並該結構利用已有的信息進行初始化;

  • 接着當該用戶的套接字可讀時,又將會去調用httpd_got_request接口,該接口將會去將套接字上的數據讀到hc結構中的read_buf中去,而後對於read_buf中的數據進行檢測,查看收到的數據是否能構成一個完整的http請求;

  • 若是接收到的確實是一個完整的http請求,就會去調httpd_parse_request接口,對read_buf中的數據進行解析,並將http頭中解析到的字段(如method,url等)放入hc結構體中。

  • 當數據都解析完成後,系統將會調用httpd_start_request接口來準備須要回覆給用戶的數據,這個數據的準備是根據解析到的具體狀況來進行處理的,有可能就是一個index.html文件,而有可能就是在hc的response中放了一些錯誤信息。

  • 而準備好要發送的數據以後,就能夠設置鏈接的狀態爲SENDING,這樣下次select後就會對於該鏈接調用handle_send函數,將數據發送出去,並關閉鏈接。

再具體的說的話,能夠看到thttpd預先開闢了大約1024個conn_tab結構,這裏的conn_tab指的是鏈接的具體信息,其數據結構核心數據以下:

typedef struct 
{
    int conn_state;
    httpd_conn* hc; //has to define
    long limit;
    time_t started_at;
    int numtnums;
    Timer* idle_read_timer;
    Timer* idle_send_timer;
    Timer* wakeup_timer;
    Timer* linger_timer;

    off_t bytes;
    off_t bytes_sent;
    off_t bytes_to_send;
} conn_tab;

其中包含了鏈接的狀態conn_state,內嵌了具體的用戶信息hc,發送限制limit,開始時間started_at,四個定時器(讀定時,寫定時,鏈接甦醒定時,鏈接保持定時),和鏈接當前要發送的字節數bytes_to_send,已經發送的字節bytes_sent,這裏的bytes好像沒有啥重要意義;

對於接收到的一個用戶而言,則按照上述的httpd_conn結構的定義,則初始化的時候須要考慮以下內容,init初始化標誌,hs服務器結構地址,本身的client地址,而後就是讀取信息須要的read_buf和用於其標記的idx和checked狀態位,而後就是解析所須要的method, encodedurl, decodeurl, protocol, origfilename, expnfilename, encodings, pathinfo, query, referer, useragent, accept, accepete, cookie, contenttype, reqhost, hdrhost, quthorization, remoteuser, 及其最大字符長度,最後就是返回須要的response_buf,file_address, contentlength,還有些標誌位信息如mimeflag,http1.1標誌one_one,keep_alive標誌,should_linger標誌以及got_range標誌。

這裏以一個普通的GET請求爲例,講一下http_conn中各字段的值分別是什麼。

HTTP REQUEST: GET /index.html?a=1 HTTP/1.1

解析完成後

method              1(GET)
protocol            HTTP/1.1
reqhost              ""
encodedurl          /index.html?a=1(可能有16進制數)
decodefurl          /index.html?a=1(沒有16進制)
origfilename        index.html (url是"/"時設爲「.」)
expnfilename        index.html (沒有符號連接,且證實了文件存在性)
pathinfo            ""
query               a=1
accept              text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

accepte             gzip, deflate, sdch
remoteuser          ""
response            存放着http頭部信息p
referer             ""
useragent           Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
cookie              ""
contenttype         ""
hdrhost             127.0.0.1
authorization       ""
id_modified_since   -1
range_if            -1
contentlength       -1
got_range            0
init_byte_loc        0
end_byte_loc         -1
keep_alive           1
should_linger        1
hostname             NULL
mime_flag            1
bytes                111(html文件的大小)
file_address         文件內存地址

其中最後反饋的數據就是由response和file_address這裏那個部分組成的。
相關文章
相關標籤/搜索