select_loop()
继续,到了这里:
if (!sigterm_flag && total_connections < (max_connections - 10)) {
BOA_FD_SET(server_s, &block_read_fdset); /* server always set */
}
如果没有收到SIGTERM信号,连接数没有超过最大连接数-10,那么继续监听server_s。
req_timeout.tv_sec = (request_ready ? 0 :
(ka_timeout ? ka_timeout : REQUEST_TIMEOUT));
req_timeout.tv_usec = 0l; /* reset timeout */
设置一会儿用于select的timeout。
select_loop()最后一段:
if (select(max_fd + 1, &block_read_fdset,
&block_write_fdset, NULL,
(request_ready || request_block ? &req_timeout : NULL)) == -1) {
/* what is the appropriate thing to do here on EBADF */
if (errno == EINTR)
continue; /* while(1) */
else if (errno != EBADF) {
DIE("select");
}
}
time(&t_time);
if (FD_ISSET(server_s, &block_read_fdset))
pending_requests = 1;
maxfd,block_write_fdset, block_read_fdset 我没有见到在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()之间客户关闭连接的情况:
#ifdef DEBUGNONINET
/* This shows up due to race conditions in some Linux kernels
when the client closes the socket sometime between
the select() and accept() syscalls.
Code and description by Larry Doolittle <ldoolitt@boa.org>
*/
#define HEX(x) (((x)>9)?(('a'-10)+(x)):('0'+(x)))
if (remote_addr.sin_family != AF_INET) {
struct sockaddr *bogus = (struct sockaddr *) &remote_addr;
char *ap, ablock[44];
int i;
close(fd);
log_error_time();
for (ap = ablock, i = 0; i < remote_addrlen && i < 14; i++) {
*ap++ = ' ';
*ap++ = HEX((bogus->sa_data[i] >> 4) & 0x0f);
*ap++ = HEX(bogus->sa_data[i] & 0x0f);
}
*ap = '\0';
fprintf(stderr, "non-INET connection attempt: socket %d, "
"sa_family = %hu, sa_data[%d] = %s\n",
fd, bogus->sa_family, remote_addrlen, ablock);
return;
}
#endif
不过,我认为没必要。如果客户端在select()和accept()之间关闭连接的话,accept应该返回EAGAIN或EWOULDBLOCK错误,然后return了。
if这段代码看起来也只是简单的检测一下客户端的sin_family是否是AF_INET。并不像处理注释说明的情况。
然后又是一小段意义不明的代码:
/* XXX Either delete this, or document why it's needed */
/* Pointed out 3-Oct-1999 by Paul Saab <paul@mu.org> */
#ifdef REUSE_EACH_CLIENT_CONNECTION_SOCKET
if ((setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &sock_opt,
sizeof (sock_opt))) == -1) {
DIE("setsockopt: unable to set SO_REUSEADDR");
}
#endif
在创建server_s时,已经设置了SO_REUSEADDR标识,不知道为什么这里要加入这么一行代码。1999年的注释……我觉着还是把这段代码删掉吧……
然后获取一个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队列的请求了,并进行相应处理了。
首先检查是否有积压数据发送:
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;
}
}
如果buffer里有数据要发送,而且状态不是DEAD不是DONE,那么调用req_flush()尝试发送数据。
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处理留到下次~