BOA代码笔记 5


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处理留到下次~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值