BOA入門分析

    經常使用的Web服務器有:Boa,thttpd,httpd,其中httpd只支持靜態頁面,thttpd和Boa支持動態頁面高級應用,Boa在資源利用效率上比thttpd好。Boa支持認證,支持CGI,做爲該嵌入式系統的Web服務器,系統的軟件開發模型選用B/S模型,嵌入式Web服務器在Web瀏覽器和設備之間提供了統一的GUI接口,客戶機能夠經過HTTP協議與嵌入式WebServer創建鏈接。html

一.Boa主流程分析數據庫

       Boa.c程序main中主要流程:編程

wKioL1Om_43xclp7AAKHj1ZzJX4595.jpg

       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_namesocket

single_post_limitcgi_rlimit_cpucgi_rlimit_datacgi_nicemax_connectionska_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 Refererheader 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

其中包含了錯誤時間內、請求客戶端IPCGI發送方式爲GET、路徑信息和錯誤因素。

 

       create_server_socket();建立服務器的套接字,SOCK_STREAM提供有序的、可靠的、雙向的和基於鏈接的字節流,IPPROTO_TCP指定了Internet地址族使用TCPset_nonblock_fd()設置非阻塞方式,fcntl(server_s, F_SETFD, 1)設定close-on-exec,調用exec函數時關閉server socket,避免CGI往裏面寫內容。而後進行bind捆綁,並監聽客戶端發送的請求。

      

       init_signals();初始化各類信號的回調函數,在多線程運行中控制程序運行。

 

       build_needs_escape();瀏覽器在發送請求時,會把請求字符串進行轉義操做,服務器要對收到的請求進行反轉移操做。

 

       do_forkfork子進程,建立守護進程,做爲服務程序使用,等待客戶端程序與它通訊。

      

       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表示block0表示處理完畢,>0表示還有數據,須要對其進行處理後再發送。

 

三.loop循環分析

首先對信號進行分析,程序調用init_signals()初始化信號回調函數,包括SIGSEGVSIGBUSSIGTERMSIGHUPSIGINTSIGCHLDSIGALRMSIGPIPESIGUSR1SIGUSR2sigaction(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(&current_time);
        }
        max_fd = -1;
        if (request_block) {
            /* 若是request_block=1,則把請求信號移到就緒鏈表*/
            fdset_update();
        }
        if (pending_requests || request_ready){
           /* 有待處理請求或者就緒請求則進行請求處理*/
            process_requests(server_s);
        }
    }
}

sighup調用sighup_run(),清除mimepasswdalias哈希表,清空request_free鏈表,並從新讀取配置文件。

sigchld_flag調用sigchld_run(),處理子進程。

sigalrm_flag調用sigalrm_run(),將mimepasswd哈希表信息寫到日誌裏。

 

SIGTERM信號,

sigterm_flag =1

void sigterm_stage1_run(void)
{                               /* lame duck mode */
   time(&current_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

       CGICommon 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頭信息,綁定pipecgi的輸入輸出,爲數據傳輸作準備,建立子進程,子進程從管道pipes[0]讀取數據,父進程寫數據到pipes[1]並輸出。

process_cgi_header(request * req);函數對瀏覽器發送的http頭信息進行處理,成爲瀏覽器能夠讀懂的字符串。服務器通過處理把結果發送回CGIcgi 程序輸出HTML頁面的方式是使用printf 把頁面一行一行地打印出來,如

fprintf(cgiOut, "<HTML><HEAD>/n"); 

fprintf(cgiOut, "<TITLE>helloworld</TITLE></HEAD>/n"): 

fprintf(cgiOut, "<BODY><H1> hello world</H1>/n"); 

信息傳送到瀏覽器並在顯示器上顯示。

 

結語:

       編程初入門,部分代碼爲猜想,內容可能出現較多錯誤,請不吝指正。

相關文章
相關標籤/搜索