select_loop()
继续,到了这里:
select_loop()最后一段:
如果request_ready或request_block标志设置了,那么select有个时间限制,否则阻塞在select中。这两个标志好像也是第一次见到,应该也是在别的函数中设置。
如果返回值为EINTR,说明被中断,continue。
对于EBADF,man select的解释是:
An invalid file descriptor was given in one of the sets. (Perhaps a file descriptor that was already closed, or one on which an error has occurred.)
boa对这个错误的处理是,忽略。
然后更新一下时间。
看一下server_s是否可读,如果可读意味着有连接,pending_requests=1。该标志之前我们已经见到过。
select_loop()我们已经看完,但还有很多东西不清楚。
根据目前的情况来看,主要的处理部分在上次分析过的fd_update()和process_requests()。
其中fd_update()已经介绍的差不多了,而process_requests()里还有很多函数没仔细看。
照这个样子来看,maxfd,block_write_fdset, block_read_fdset的修改,所有请求的处理等内容应该都在process_requests()里了。感觉很难的样子。。。
再看process_requests()
上篇也提到了,process_requests()上来就先检查是否有pending_requests。如果有,调用 get_request(server_s);,就像这样子:
if (pending_requests) {
get_request(server_s);
#ifdef ORIGINAL_BEHAVIOR
pending_requests = 0;
#endif
}
那个ORIGINAL_BEHAVIOR是啥,我没在源代码里发现。也许是编译时加上-D调试用。。。
之前略了,现在看看get_requests()的过程吧
get_requests()
上来就fd = accept(server_s, (struct sockaddr *) &remote_addr, &remote_addrlen);
接受客户端,保存相关信息到remote_addr里。
注意,之前server_s已经设为nonblock模式,所以这个accept也是非阻塞的,也就有了如下错误处理:
if (fd == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
/* abnormal error */
WARN("accept");
else
/* no requests */
pending_requests = 0;
return;
}
如果错误是EAGAIN或EWOULDBLOCK,说明没有请求。那么不把他当错误处理,pending_requests=0。
POSIX要求这两个错误相等。
之后,一小段代码说是用来处理select()和accept()之间客户关闭连接的情况:
if这段代码看起来也只是简单的检测一下客户端的sin_family是否是AF_INET。并不像处理注释说明的情况。
然后又是一小段意义不明的代码:
然后获取一个request结构体来管理请求:conn = new_request();
如果request_free链表有空闲的request,那么从链表里分配。如果没有空闲的request,那么malloc一个。这样做可以减少malloc、free的次数。
对conn进行如下初始化:
conn->fd = fd; //设置fd
conn->status = READ_HEADER; //设置状态为READ_HEADER
conn->header_line = conn->client_stream; //设置HTTP请求头部指针指向客户数据流的缓冲区地址(此时还没有接受数据)
conn->time_last = current_time; //设置最后活动时间
conn->kacount = ka_max; //设置keeplive acount
可见,一个请求到来后,首先的状态是READ_HEADER。
将conn->fd设置为nonblock。
将conn->fd设置为CLOSE-ON-EXEC。
最后,检查一下SO_SNDBUF,如果系统默认的SO_SNDBUF小于软件自定义的sockbufsize,那么更新系统的SO_SNDBUF。
此项操作只执行一次。
回到process_requests()
接下来,就该遍历所有的request_ready队列的请求了,并进行相应处理了。
首先检查是否有积压数据发送:
req_flush不贴代码了,大体流程如下:
获得要发送字节数:bytes_to_write = req->buffer_end - req->buffer_start;;
如果没有字节数为0,那么req->buffer_end = req->buffer_start = 0;然后返回0,成功。
否则尝试写数据:bytes_written = write(req->fd, req->buffer + req->buffer_start, bytes_to_write);
如果write成功,然后返回0(表示write完毕)或者buffer_end(表示还没写完),成功。
如果write错误,
如果是EAGAIN,表示需要block,返回-1,表示需要block
如果其他错误,req->buffer_start = req->buffer_end = 0; 状态设置为DEAD,返回-2,表示失败。
req_flush返回值-2表示错误,-1表示block,0表示处理完毕,>0表示还有数据
之后对req_flush的rtval进行规范化,以符合process_requests()里对rtval的约定,方面最后统一处理rtval:
if (retval == -2) { current->status = DEAD; retval = 0; } else if (retval >= 0) { retval = 1; }
process_requests()对rtval的约定之前提到过:返回值-1表示需要进入block queue;返回值0表示请求结束;返回值1表示还要在ready queue里。
先到这里,去看会儿动画,后边的switch分支和最终的rtval处理留到下次~