boa源碼分析(3)--代碼結構

1  boa.c

主程序:

----1)  關閉文件   
for(i=3;i<=1024;i++)  
      close(i);

----2)  設置進程權限掩碼 
umask(~0600);    rw- --- ---;

----3)  打開黑洞,並將標準輸入輸出指向它,  
open("/dev/null", 0);
dup2(devnullfd, STDIN_FILENO);
dup2(devnullfd, STDOUT_FILENO);

 ----4)  更新時間戳,日誌要用到。
time(¤t_time);

----5)解析命令行
-f
server_root = strdup(optaarg);
-r  
chdir(optarg);   
chroot(optarg);
chdir("/");
-d 
do_fork=0;

----6)//確保服務器根目錄是有效的。
fixup_server_root();

----7)//讀取配置文件通過yyparse 確保必要的變量設置正確。
read_config_files();

----8)//打開access log,error log, [cgi log] ;並設置 close-on-exec爲真,即在exec調用後,關閉文件描述符。
open_logs();

----9)//創建TCP socket,設置爲nonblock ,同樣設置 close-on-exec爲真,這樣,EXEC調用時,cgi不能向它寫入。。。
server_s = create_server_socket();    //打開了地址複用功能 詳見 unix網絡編程。

---10)//指定各信號的handle
init_signals();

---11)//設置用戶ID和進程組ID。
drop_privs();//降 特權

---12) Set up the environment variables that are common to all CGI scripts
 create_common_env();

---13)  fork子進程,父進程退出。之後子進程成爲守護進程
if(do_fork)   switch(fork())

---14) 得到PID,用於產生獨一無二的臨時文件名或路徑。
int pid = getpid();

---15)  更新時間戳,然後進入主循環。
timestamp();
select_loop(server_s)
{
  1)清空,block_read_fdset、block_write_fdset;
  2)設置server_s和請求超時時間。
  3)進入while(1)
  {
     1)   處理sighup 、  sigchld 、 sigalrm、 sigterm等信號。
      2)重設max_fd = -1;
     3)   將合適的request從block鏈表裏移到ready鏈表裏。
           if(reques_block)     fdset_update(); //

     4)  process_requests(server_s);

     5)  if (!sigterm_flag && total_connections < (max_connections - 10))          BOA_FD_SET(server_s, &block_read_fdset); /* server always set */

    6)   reset  timeout

     7)   select調用,select(max_fd + 1, &block_read_fdset, &block_write_fdset, NULL, (request_ready || request_block ? &req_timeout : NULL))

    8)更新當前時間,time(&curent_time);
    9)  if (FD_ISSET(server_s, &block_read_fdset))   pending_requests = 1;

  }

}


 一、先來看看 fdset_update()

boa裏邊有三個請求鏈表

request *request_ready = NULL;  /* ready list head */
request *request_block = NULL;   /* blocked list head */
request *request_free = NULL;      /* free list head */

struct request {                /* pending requests */
    int fd;                     /* client's socket fd */
    int status;                 /* see #defines.h */
    time_t time_last;           /* time of last succ. op. */
    char *pathname;             /* pathname of requested file */
    int simple;                 /* simple request? */
    int keepalive;              /* keepalive status */
    int kacount;                /* keepalive count */

    int data_fd;                /* fd of data */
    unsigned long filesize;     /* filesize */
    unsigned long filepos;      /* position in file */
    char *data_mem;             /* mmapped/malloced char array */
    int method;                 /* M_GET, M_POST, etc. */

    char *logline;              /* line to log file */

    char *header_line;          /* beginning of un or incompletely processed header line */
    char *header_end;           /* last known end of header, or end of processed data */
    int parse_pos;              /* how much have we parsed */
    int client_stream_pos;      /* how much have we read... */

    int buffer_start;           /* where the buffer starts */
    int buffer_end;             /* where the buffer ends */

    char *http_version;         /* HTTP/?.? of req */
    int response_status;        /* R_NOT_FOUND etc. */

    char *if_modified_since;    /* If-Modified-Since */
    time_t last_modified;       /* Last-modified: */

    char local_ip_addr[NI_MAXHOST]; /* for virtualhost */

    /* CGI vars */

    int remote_port;            /* could be used for ident */

    char remote_ip_addr[NI_MAXHOST]; /* after inet_ntoa */

    int is_cgi;                 /* true if CGI/NPH */
    int cgi_status;
    int cgi_env_index;          /* index into array */

    /* Agent and referer for logfiles */
    char *header_user_agent;
    char *header_referer;

    int post_data_fd;           /* fd for post data tmpfile */

    char *path_info;            /* env variable */
    char *path_translated;      /* env variable */
    char *script_name;          /* env variable */
    char *query_string;         /* env variable */
    char *content_type;         /* env variable */
    char *content_length;       /* env variable */

    struct mmap_entry *mmap_entry_var;

    struct request *next;       /* next */
    struct request *prev;       /* previous */

    /* everything below this line is kept regardless */
    char buffer[BUFFER_SIZE + 1]; /* generic I/O buffer */
    char request_uri[MAX_HEADER_LENGTH + 1]; /* uri */
    char client_stream[CLIENT_STREAM_SIZE]; /* data from client - fit or be hosed */
    char *cgi_env[CGI_ENV_MAX + 4];             /* CGI environment */

#ifdef ACCEPT_ON
    char accept[MAX_ACCEPT_LENGTH]; /* Accept: fields */
#endif
};

typedef struct request request;


 

static void fdset_update(void)
{
	request *current, *next;

	for (current = request_block; current; current = next)
	{
		time_t time_since = current_time - current->time_last;
		next = current->next;
		
		/* hmm, what if we are in "the middle" of a request and not
		 * just waiting for a new one... perhaps check to see if anything
		 * has been read via header position, etc... */
		if (current->kacount < ka_max && /* we *are* in a keepalive */
		(time_since >= ka_timeout) && /* ka timeout */
		!current->logline) /* haven't read anything yet */
			current->status = DEAD; /* connection keepalive timed out */
		else if (time_since > REQUEST_TIMEOUT)
		{
			log_error_doc(current);
			fputs("connection timed out\n", stderr);
			current->status = DEAD;
		}
		if (current->buffer_end && current->status < DEAD)
		{
			if (FD_ISSET(current->fd, &block_write_fdset))
				ready_request(current);
			else
			{
				BOA_FD_SET(current->fd, &block_write_fdset);
			}
		} else
		{
			switch (current->status)
			{
			case WRITE:
			case PIPE_WRITE:
				if (FD_ISSET(current->fd, &block_write_fdset))
					ready_request(current);
				else
				{
					BOA_FD_SET(current->fd, &block_write_fdset);
				}
				break;
			case BODY_WRITE:
				if (FD_ISSET(current->post_data_fd, &block_write_fdset))
					ready_request(current);
				else
				{
					BOA_FD_SET(current->post_data_fd, &block_write_fdset);
				}
				break;
			case PIPE_READ:
				if (FD_ISSET(current->data_fd, &block_read_fdset))
					ready_request(current);
				else
				{
					BOA_FD_SET(current->data_fd, &block_read_fdset);
				}
				break;
			case DONE:
				if (FD_ISSET(current->fd, &block_write_fdset))
					ready_request(current);
				else
				{
					BOA_FD_SET(current->fd, &block_write_fdset);
				}
				break;
			case DEAD:
				ready_request(current);
				break;
			default:
				if (FD_ISSET(current->fd, &block_read_fdset))
					ready_request(current);
				else
				{
					BOA_FD_SET(current->fd, &block_read_fdset);
				}
				break;
			}
		}
		current = next;
	}
}


for循環裏面,

首先,獲取time_since爲距離上次成功操作經歷的時間。

如果請求出於keepalive中,time_since已經大於ka_timeout(配置文件裏可以配置),而且還沒有讀取到任何東西,那麼request的status變爲DEAD。

如果time_since大於REQUEST_TIMEOUT(60),那麼status變爲DEAD。

如果緩衝區有數據,而且status小於DEAD:

        如果不在block_write_fdset裏,那麼放到block_write_fdset裏。

        如果fd已經在block_write_fdset裏,調用ready_request,將request從block隊列裏轉移到ready隊列裏,同時清除block_write_fdset裏的標誌

ready_request函數的功能是根據status,從fdset中清除對應fd。

其他情況:

        狀態爲WRITE,PIPE_WRITE,DONE的請求,如果沒有那就放到block_write_fdset裏,如果已經在了就調用ready_request。

        狀態爲BODY_WRITE,將request的post_data_fd做以上處理。post_data_fd註釋爲/* fd for post data tmpfile */,應該是客戶端POST方法時的臨時文件        

        狀態爲PIPE_READ,將request的data_fd做類似處理,不過檢查的是block_read_fdset。

        狀態爲DEAD,直接調用ready_request。

        其他的,檢查fd是否在block_read_fdset,並作相應處理。

 

 二、再看看process_erquests函數。

void process_requests(int server_s)
{
    int retval = 0;
    request *current, *trailer;

    if (pending_requests) {
        get_request(server_s);
#ifdef ORIGINAL_BEHAVIOR
        pending_requests = 0;
#endif
    }

    current = request_ready;

    while (current) {
        time(¤t_time);
        if (current->buffer_end && /* there is data in the buffer */
            current->status != DEAD && current->status != DONE) {
            retval = req_flush(current);
            /*
             * retval can be -2=error, -1=blocked, or bytes left
             */
            if (retval == -2) { /* error */
                current->status = DEAD;
                retval = 0;
            } else if (retval >= 0) {
                /* notice the >= which is different from below?
                   Here, we may just be flushing headers.
                   We don't want to return 0 because we are not DONE
                   or DEAD */

                retval = 1;
            }
        } else {
            switch (current->status) {
            case READ_HEADER:
            case ONE_CR:
            case ONE_LF:
            case TWO_CR:
                retval = read_header(current);
                break;
            case BODY_READ:
                retval = read_body(current);
                break;
            case BODY_WRITE:
                retval = write_body(current);
                break;
            case WRITE:
                retval = process_get(current);
                break;
            case PIPE_READ:
                retval = read_from_pipe(current);
                break;
            case PIPE_WRITE:
                retval = write_from_pipe(current);
                break;
            case DONE:
                /* a non-status that will terminate the request */
                retval = req_flush(current);
                /*
                 * retval can be -2=error, -1=blocked, or bytes left
                 */
                if (retval == -2) { /* error */
                    current->status = DEAD;
                    retval = 0;
                } else if (retval > 0) {
                    retval = 1;
                }
                break;
            case DEAD:
                retval = 0;
                current->buffer_end = 0;
                SQUASH_KA(current);
                break;
            default:
                retval = 0;
                fprintf(stderr, "Unknown status (%d), "
                        "closing!\n", current->status);
                current->status = DEAD;
                break;
            }

        }

        if (sigterm_flag)
            SQUASH_KA(current);

        /* we put this here instead of after the switch so that
         * if we are on the last request, and get_request is successful,
         * current->next is valid!
         */
        if (pending_requests)
            get_request(server_s);

        switch (retval) {
        case -1:               /* request blocked */
            trailer = current;
            current = current->next;
            block_request(trailer);
            break;
        case 0:                /* request complete */
            current->time_last = current_time;
            trailer = current;
            current = current->next;
            free_request(&request_ready, trailer);
            break;
        case 1:                /* more to do */
            current->time_last = current_time;
            current = current->next;
            break;
        default:
            log_error_time();
            fprintf(stderr, "Unknown retval in process.c - "
                    "Status: %d, retval: %d\n", current->status, retval);
            current = current->next;
            break;
        }
    }
}

 

對於每一個ready queue裏的請求遍歷處理,返回值-1表示需要進入block queue;返回值0表示請求結束;返回值1表示還要在ready queue裏,需進一步處理。

首先檢查是否有pending_requests,如果有調用get_request(server_s);,接受一個connection,加入ready_queue。

get_request(server_s);大體功能是,接受一個請求,並做一些簡單的初始化,加入ready_queue。

然後開始輪詢ready鏈表:

如果有數據要寫,狀態不是DEAD或DONE,就調用req_flush(current)。

每一輪最後檢查一次,是否還有pending_requests。有的話加入ready_queue。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章