1、主函數模塊分析
對於主函數而言,概括來說主要做了三點內容,也就是初始化系統,進行系統大循環,退出系統。下面主要簡單闡述下在這三個部分,又做了哪些工作呢。
初始化系統
-
拿出程序的名字(argv[0])用來作為參數打開那個log(syslog)
-
解析命令行的參數(parse_args),初始化內部的參數變量
-
檢查當前主機名(addr) 沒有的話利用gethostbyname從hostname中獲取
-
檢查當前要使用的主機端口(port)
-
讀取Throttle file(門限文件,這里省略)
-
檢查logfile的值,有的話就創建一個logfp咯
-
獲得系統用戶的相關信息(getpwnam),使用的系統用戶為nobody(安全),記錄下uid,gid值
-
切換程序的工作空間為參數中的dir值
-
獲得當前工作目錄(保證以'/'結尾)
-
調用daemon函數進入后台工作
-
查看pidfile,如果pidfile不為空則打開該文件,寫入pid值
-
根據參數選擇是否chroot,(chroot的原因見這個鏈接)
-
設置信號處理函數signal(處理SIGTERM SIGINT SIGPIPE SIGHUP SIGUSR1)
-
初始化http處理模塊(調用該模塊init函數)
-
設置一個occasional timer用於時不時的清除定時器模塊和mmc模塊的無用內存,如果有需要的話,設置一個status timer,用於記錄狀態
-
為了安全,放棄root權限,變成nobody(使用setgroups和setgid,setuid函數族)
-
利用fdwatch包裝的api,獲得最多可以復用的fd數
-
創建一個連接池(數組)每個連接的數據結構如下,並完成初始化操作。
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;
系統大循環
-
獲得當前的時間,開始大循環
-
循環的條件時 terminate != 0 || numconnects > 0
-
循環的內容是:
- 如果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時的處理
如果有數據直接讀取數據扔掉
退出系統
- 清除已分配的內存
- 關閉系統日志
- 退出
2、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這里那個部分組成的。