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。