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。

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