process_requests()
昨天分析到了switch部分。
process_requests()还剩下的,也就是处理请求的switch部分和处理retval的swtich部分。先看下剩下这部分的源码:
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;
}
如果状态是DONE的话,那么调用req_flush()函数清空需要向客户端发送的数据,req_flush函数上次已经分析。
如果状态是DEAD的话,那么将buffer清空,然后保活计时器清0,retval置0表示不再处理。
其他状态调用相应的处理函数。 之后再看。
之后检查一下是否收到了SIGTERM;如果有pending_requests做相应处理。
然后就到了对返回值的处理:
如果retval为-1,表示需要block,那么调用block_request(trailer);。
如果retval为0 ,表示请求不需要处理了,调用free_request(&request_ready, trailer);关闭连接,释放request结构。
如果retval为1 ,表示不需要block,但需要继续处理,那么只是简单的更新一下活跃时间time_last。
block_request()和free_requests()
block_request(trailer);先将任务从ready队列中取出,然后插入block队列。然后根据status设置相应的block_write_fdset或block_read_fdset。
free_requests的代码比较长,总体上看,功能是释放掉request占用的内存,关闭socket。大体流程是:
让request出队;
需要的话,做个记录;
如果有共享的mmap映射内存,那么引用计数-1,到0munmap掉;如果没有共享的mmap映射,检查是否有私有的mmap内存,有的话munmap掉。
如果有data_fd或post_data_fd,那么关掉它;
将请求关联的所有cgi的环境变量env释放掉;
将各种动态申请的字符串释放掉;
不明白接下来这个if什么目的,代码也没有注释:
如果 ((req->keepalive == KA_ACTIVE) && (req->response_status < 500) && req->kacount > 0) 将进行如下操作:新分配一个request结构conn,将各种信息从req里复制到新配分的conn里。将req结构放到free链表里,返回。
然后对一种遗留问题进行处理:CERN服务器要求POST请求最后加一行额外的CRLF,这个不算在content-length里。如果请求类型是M_POST,那么试图读取掉剩余的信息。
最后关闭连接,将req结构踢加入free链表。
总结
至此,只剩下process_select()调用的:
read_header(current);
read_body(current);
write_body(current);
process_get(current);
read_from_pipe(current);
write_from_pipe(current);
他们分析HTTP请求,进行相应的处理。
博主目前的复习重点还是在基础c/s模型的掌握上,这些函数就先略过了,为了找工作还有许多别的东西要学要复习。
boa的整体流程基本了解了。总结一下:
主要数据结构:
每个连接分配一个reqeust结构体,里边包含着连接的各种上下文信息,由于数据成员很多,这里只列举部分:
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 */
int client_stream_pos; /* how much have we read... */
int buffer_start; /* where the buffer starts */
int buffer_end; /* where the buffer ends */
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 *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 client_stream[CLIENT_STREAM_SIZE]; /* data from client - fit or be hosed */
char *cgi_env[CGI_ENV_MAX + 4]; /* CGI environment */
};
extern request *request_ready; /* first in ready list */
extern request *request_block; /* first in blocked list */
extern request *request_free; /* first in free list */
boa里,对请求的操作是以这三个链表为中心,而不是以select为驱动。这不同于《Unix网络编程》上的简单例子,一般都是select为主。在这个程序里,是以对前两个链表的扫描、更新为主要任务。free链表用来减少malloc和free request结构体的次数。
两个fdset:
extern fd_set block_read_fdset; /* fds blocked on read */
extern fd_set block_write_fdset; /* fds blocked on write */
这两个fdset用作select参数,在process_requests()和fdset_update()里,会对这两个fdset进行设置。
主要算法流程:
获取参数
读配置文件
打开日志
创建server_s
建立信号处理机制
成为daemon进程
select循环
检查各种信号是否发生。
将阻塞队列的请求更新到就绪队列。
处理就绪队列的请求,并进行相应处理。block_read_fdset和block_write_fdset的更新在这两个函数里进行。
设置server_s,并调用select
如果有新连接,置pending_requests为1,延时到fdset_update或process_requests里处理。
boa代码分析暂告一段落,以后如果别的事情做的差不多了,而且时间足够,应该会继续看完。
fighting~ :)