經常使用的Web服務器有:Boa,thttpd,httpd,其中httpd只支持靜態頁面,thttpd和Boa支持動態頁面高級應用,Boa在資源利用效率上比thttpd好。Boa支持認證,支持CGI,做爲該嵌入式系統的Web服務器,系統的軟件開發模型選用B/S模型,嵌入式Web服務器在Web瀏覽器和設備之間提供了統一的GUI接口,客戶機能夠經過HTTP協議與嵌入式WebServer創建鏈接。html
一.Boa主流程分析數據庫
Boa.c程序main中主要流程:編程
umask()函數設置限制新文件權限的掩碼,umask設置的是權限是補碼,用戶登錄系統時,umask命令都被執行,並自動設置掩碼改變默認值,新的權限將會把舊的覆蓋。瀏覽器
devnullfd = open("/dev/null", 0);dup2(devnullfd,STDIN_FILENO;對輸入重定向,寫入/dev/null的東西會被系統丟掉,目的是對stdin進行保護。安全
parse_commandline(argc, argv);執行命令行,其中case'r':chdir(optarg);chroot(optarg);chdir("/");從新設定根目錄,限制用戶訪問路徑,提升服務器安全性。服務器
fixup_server_root();用來檢查服務器路徑有沒有被定義,若是沒定義就中止執行並提示。多線程
read_config_files(void);解析配置文件,保證全部的全局變量正確初始化.gethostname函數返回本地主機的標準主機名,並經過gethostbyname()返回該主機名的包含主機名字和地址信息的hostent結構指針,主機名保存到server_name。socket
single_post_limit、cgi_rlimit_cpu、cgi_rlimit_data、cgi_nice、max_connections、ka_timeout等變量與程序資源分配有關。ide
rlimit資源限制參考http://blog.csdn.net/yuyin86/article/details/8014840。函數
create_common_env(void);把CGI的環境變量保存到*common_env[]內,環境變量如"PATH"、"SERVER_SOFTWARE"、"SERVER_PORT"、"DOCUMENT_ROOT"、"SERVER_ROOT"、"SERVER_ADMIN"。
open_logs(void);程序主要記錄access_log訪問信息、error_log錯誤信息和cgi_log信息。
access_log記錄了虛擬主機地址、訪問客戶端地址、訪問時間、訪問狀態、讀寫數據大小、HTTP Referer、header user agent等,HTTP Referer記錄了外鏈接狀況,user agent用戶代理是瀏覽器向訪問網站提供瀏覽器信息的一種HTTP標誌。
error_log記錄效果:[08/Nov/1997:01:05:03-0600] request from 192.228.331.232 "GET /~joeblow/dir/ HTTP/1.0"("/usr/user1/joeblow/public_html/dir/"): write: Broken pipe
其中包含了錯誤時間內、請求客戶端IP、CGI發送方式爲GET、路徑信息和錯誤因素。
create_server_socket();建立服務器的套接字,SOCK_STREAM提供有序的、可靠的、雙向的和基於鏈接的字節流,IPPROTO_TCP指定了Internet地址族使用TCP,set_nonblock_fd()設置非阻塞方式,fcntl(server_s, F_SETFD, 1)設定close-on-exec,調用exec函數時關閉server socket,避免CGI往裏面寫內容。而後進行bind捆綁,並監聽客戶端發送的請求。
init_signals();初始化各類信號的回調函數,在多線程運行中控制程序運行。
build_needs_escape();瀏覽器在發送請求時,會把請求字符串進行轉義操做,服務器要對收到的請求進行反轉移操做。
do_fork,fork子進程,建立守護進程,做爲服務程序使用,等待客戶端程序與它通訊。
drop_privs();經過修改服務進程ID、組ID、改變權限。
loop(int server_s),是一個select循環,Web服務器正常運行在循環中,循環檢測各類信號發生,根據須要修改請求狀態(阻塞和就緒),並做相應的處理,後文詳細描述。
二.request請求處理
request結構:
struct request { enum REQ_STATUS status; /*請求狀態,包括BODY_READ,BODY_WRITE,WRITE,PIPE_READ,PIPE_WRITE,IOSHUFFLE,DONE,TIMED_OUT,DEAD*/ enum KA_STATUS keepalive; /* keepalive status 表示鏈接狀態,是檢測死鏈接的一種機制 */ enum HTTP_VERSION http_version; enum HTTP_METHOD method; /* M_GET, M_POST, etc.HTTP與瀏覽器的交互,從url讀取信息的方法 */ enum RESPONSE_CODE response_status; /* HTTP碼應碼. 1xx:信息,請求收到,繼續處理 2xx:成功,行爲被成功地接受、理解和採納 3xx:重定向,爲了完成請求,必須進一步執行的動做 4xx:客戶端錯誤,請求包含語法錯誤或者請求沒法實現 5xx:服務器錯誤,服務器不能實現一種明顯無效的請求 */ enum CGI_TYPE cgi_type; enumCGI_STATUS cgi_status; char *pathname; /* 請求文件路徑名 */ Range *ranges; /* 請求內容搜索範圍 */ int data_fd; /* 數據文件描述符 */ unsigned long filesize; /* 文件大小*/ unsigned long filepos; /* position in file */ unsigned long bytes_written; /* 被寫入長度*/ char *data_mem; /* 數據映射*/ char *header_line; /* beginning of un or incompletelyprocessed header line */ char *header_end; /* last known end of header, or endof processed data */ int parse_pos; /* how much have we parsed */ int buffer_start; /* buffer開始地址*/ int buffer_end; /* buffer結束地址 */ char *if_modified_since; /* 判斷服務器端的資源是否被修改 */ time_t last_modified; /* 服務器端的資源修改時間 */ int cgi_env_index; /* CGI變量排序*/ /* Agent and referer for logfiles */ char *header_host; /*主機信息*/ char *header_user_agent; /*代理信息*/ char *header_referer; /*參考信息*/ char *header_ifrange; char *host; int post_data_fd; /* post數據臨時文件描述符 */ /* env variable */ char *path_info; char *path_translated; char *script_name; char *query_string; char *content_type; char *content_length; struct mmap_entry *mmap_entry_var; int fd; /* 客戶端套接字描述符 */ time_t time_last; /* 上一次時間 */ char local_ip_addr[BOA_NI_MAXHOST]; /* 本地虛擬主機IP地址*/ char remote_ip_addr[BOA_NI_MAXHOST]; /* 遠程客戶端IP地址 */ unsigned int remote_port; /* 遠程客戶端端口號 */ unsigned int kacount; /*存活鏈接數目*/ int client_stream_pos; char buffer[BUFFER_SIZE + 1]; /* 通用I/O緩衝數據*/ char request_uri[MAX_HEADER_LENGTH + 1]; /*uri*/ char client_stream[CLIENT_STREAM_SIZE]; /* 客戶端發送的數據流 */ char *cgi_env[CGI_ENV_MAX + 4]; /* CGI 環境變量 */ #ifdef ACCEPT_ON char accept[MAX_ACCEPT_LENGTH]; /* Accept:fields */ #endif struct request *next; /* 下一個請求*/ struct request *prev; /*上一個請求 */ };
request狀態分別是獲取、就緒、執行、阻塞、釋放。
與request相關的變量:
pending_requests 待處理請求,爲1時表示有請求須要處理。
request *request_ready 就緒請求,就緒狀態的請求能夠立刻處理。
request *request_block 阻塞請求,阻塞的請求處於等待狀態,須要先移到就緒區才能進行處理。
request *request_free 空閒的請求空間。
與其相關函數有
ready_request ( ),block_request( )。
update_blocked();fd_update(),process_requests()。
get_requests( ); free_request( );
req_flush();
ready_request(request * req),把請求從阻塞鏈表移到就緒鏈表,並刪除對應req->status的文件描述符集合。
fd_update()先判斷是否爲死連接,而後調用update_blocked完成請求就緒,並刪除文件描述符集合的操做。
block_request(request* req),把請求從就緒鏈表移到阻塞鏈表,並根據req->status添加文件描述符的集合。
update_blocked();根據存活時間判斷阻塞鏈表內請求的狀態更新阻塞鏈表,決定是否把請求從阻塞鏈表移到就緒鏈表。
get_requests( );經過accept接收客戶端鏈接信息,進行鏈接之後調用new_request()申請請求空間,開始接收客戶端瀏覽器發送的請求。
請求信息例:
conn->fd = fd; conn->status = READ_HEADER; conn->header_line =conn->client_stream; conn->time_last = current_time; conn->kacount = ka_max; conn->remote_port = net_port(&remote_addr); conn->http_version= HTTP10; conn->method = M_GET; conn->status = DONE;
free_request(request * req); 與get_requests( )相反,負責關閉鏈接,釋放請求空間。
process_requests()執行請求,首先判斷是否有請求,若是有請求則進行鏈接並接收請求。根據current->status進行
read_header(current);
read_body(current);
write_body(current);
process_get(current);
read_from_pipe(current);
write_from_pipe(current);
執行DONE時調用req_flush(),req_flush函數計算須要發送數據的字節,字節數大於0則進行發送bytes_written = write(req->fd, req->buffer +req->buffer_start,bytes_to_write);
發送後返回整數值, -2表示錯誤,-1表示block,0表示處理完畢,>0表示還有數據,須要對其進行處理後再發送。
三.loop循環分析
首先對信號進行分析,程序調用init_signals()初始化信號回調函數,包括SIGSEGV,SIGBUS,SIGTERM,SIGHUP,SIGINT,SIGCHLD,SIGALRM,SIGPIPE,SIGUSR1,SIGUSR2。sigaction(SIGSEGV, &sa, NULL);sigaction函數是用做檢查/修改與指定信號相關聯的處理動做.
函數分析
void loop(int server_s) { FD_ZERO(BOA_READ);//每次循環都要清空集合,不然不能檢測描述符變化 FD_ZERO(BOA_WRITE); max_fd = -1; /*捕捉信號,並調用對應函數進行處理*/ while (1) { if (sighup_flag) sighup_run(); if (sigchld_flag) sigchld_run(); if (sigalrm_flag) sigalrm_run(); if (sigterm_flag) { if (sigterm_flag == 1) { sigterm_stage1_run(); BOA_FD_CLR(req, server_s,BOA_READ); close(server_s); /* make sure the server isn'tin the block list */ server_s = -1; } if (sigterm_flag == 2 &&!request_ready && !request_block) { sigterm_stage2_run(); /*terminal */ } } else { if (total_connections >max_connections) { /*鏈接數太多,則清除一個文件描述符集合*/ BOA_FD_CLR(req, server_s,BOA_READ); } else { /*建立一個文件描述符集合*/ BOA_FD_SET(req, server_s,BOA_READ); /* server always set */ } } pending_requests = 0; if (max_fd) { struct timeval req_timeout; /*timeval for select */ req_timeout.tv_sec = (request_ready ? 0 :default_timeout); req_timeout.tv_usec = 0l; /* resettimeout */ /* 監視咱們須要監視的文件描述符的變化狀況,讀寫或異常Select函數使用參考http://genime.blog.163.com/blog/static/1671577532012418341877/ */ if (select(max_fd + 1, BOA_READ, BOA_WRITE, NULL, (request_ready ||request_block ? &req_timeout :NULL)) == -1) { if (errno == EINTR) continue; /* while(1) */ else if (errno != EBADF) { DIE("select"); } } if (!sigterm_flag &&FD_ISSET(server_s, BOA_READ)) { pending_requests = 1; } time(¤t_time); } max_fd = -1; if (request_block) { /* 若是request_block=1,則把請求信號移到就緒鏈表*/ fdset_update(); } if (pending_requests || request_ready){ /* 有待處理請求或者就緒請求則進行請求處理*/ process_requests(server_s); } } }
sighup調用sighup_run(),清除mime、passwd、alias哈希表,清空request_free鏈表,並從新讀取配置文件。
sigchld_flag調用sigchld_run(),處理子進程。
sigalrm_flag調用sigalrm_run(),將mime、passwd哈希表信息寫到日誌裏。
SIGTERM信號,
sigterm_flag =1時
void sigterm_stage1_run(void) { /* lame duck mode */ time(¤t_time); log_error_time(); fputs("caught SIGTERM, starting shutdown\n", stderr); sigterm_flag =2; }
sigterm_flag =2時,進行關閉操做。
void sigterm_stage2_run(void) { /* lame duckmode */ log_error_time(); fprintf(stderr, "exiting Boa normally (uptime %d seconds)\n", (int)(current_time - start_time)); chdir(tempdir); clear_common_env(); //清空環境變量 dump_mime(); //清空mime_hashtable dump_passwd(); //清空passwd_hashtable dump_alias(); //清空alias free_requests(); //釋放請求空間 range_pool_empty(); //清空隊列 free(server_root); //釋放服務器 free(server_name); server_root =NULL; exit(EXIT_SUCCESS); //退出。 }
Loop循環先進行信號捕獲,若是捕獲到信號進行相應的操做,若是沒有則進行請求的處理:首先檢測是否有待處理請求,當一個請求到來時,將建立一個子進程爲用戶的鏈接服務。根據請求的不一樣,服務器返回HTML文件或者經過CGI調用外部應用程序,返回處理結果。服務器經過CGI與外部程序和腳本之間進行交互,根據客戶端在進行請求時所採起的方法,服務器會收集客戶所提供的信息,並將該部分信息發送給指定的CGI擴展程序。CGI擴展程序進行信息處理並將結果返回服務器,而後服務器對信息進行分析,並將結果發送回客戶端。
四.CGI
CGI(Common Gateway Interface)是外部應用擴展應用程序與WWW服務器交互的一個標準接口。按照CGI標準編寫的外部擴展應用程序能夠處理客戶端瀏覽器輸入的數據,從而完成客戶端與服務器的交互操做。而CGI規範就定義了Web服務器如何向擴展應用程序發送消息,在收到擴展應用程序的信息後又如何進行處理等內容。經過CGI能夠提供許多靜態的HTML網頁沒法實現的功能,好比搜索引擎、基於Web的數據庫訪問等等。
服務器程序能夠經過三種途徑接收信息:環境變量、命令行和標準輸入.
switch (req->method) { case M_POST: w ="POST"; //環境變量 break; case M_HEAD: //標準輸入 w ="HEAD"; break; case M_GET: //命令行 w ="GET"; break; default: w ="UNKNOWN"; break; }
BOA使用get方法得到靜態文檔。調用init_get(request * req)
先打開目標文件目錄
memcpy(gzip_pathname, req->pathname, len);
data_fd = open(gzip_pathname, O_RDONLY);
讀取文件信息,而後根據req結構更新文件信息,並把數據寫入req->data_mem
memcpy(req->buffer + req->buffer_end, req->data_mem+ r->start, bytes_free);
修改後調用process_get().
BOA使用POST方法處理較大的數據以及動態腳本,程序調用create_common_env(void)經過env_gen_extra( )函數讀取環境變量"PATH"、"SERVER_SOFTWARE"、"SERVER_PORT"、"DOCUMENT_ROOT"、"SERVER_ROOT"、"SERVER_ADMIN"的值並保存在**common_env內.
add_to_common_env( )增長環境變量項,clear_common_env()刪除環境變量項。
init_cgi(request * req)函數處理HTTP頭信息,綁定pipe與cgi的輸入輸出,爲數據傳輸作準備,建立子進程,子進程從管道pipes[0]讀取數據,父進程寫數據到pipes[1]並輸出。
process_cgi_header(request * req);函數對瀏覽器發送的http頭信息進行處理,成爲瀏覽器能夠讀懂的字符串。服務器通過處理把結果發送回CGI,cgi 程序輸出HTML頁面的方式是使用printf 把頁面一行一行地打印出來,如
fprintf(cgiOut, "<HTML><HEAD>/n");
fprintf(cgiOut, "<TITLE>helloworld</TITLE></HEAD>/n"):
fprintf(cgiOut, "<BODY><H1> hello world</H1>/n");
信息傳送到瀏覽器並在顯示器上顯示。
結語:
編程初入門,部分代碼爲猜想,內容可能出現較多錯誤,請不吝指正。