BOA代码笔记 5

select_loop()

继续,到了这里:

  1. if (!sigterm_flag && total_connections < (max_connections - 10)) {  
  2.             BOA_FD_SET(server_s, &block_read_fdset); /* server always set */  
  3.         }  
如果没有收到SIGTERM信号,连接数没有超过最大连接数-10,那么继续监听server_s。

  1. req_timeout.tv_sec = (request_ready ? 0 :  
  2.                               (ka_timeout ? ka_timeout : REQUEST_TIMEOUT));  
  3.         req_timeout.tv_usec = 0l;   /* reset timeout */  
设置一会儿用于select的timeout。


select_loop()最后一段:

  1. if (select(max_fd + 1, &block_read_fdset,  
  2.                    &block_write_fdset, NULL,  
  3.                    (request_ready || request_block ? &req_timeout : NULL)) == -1) {  
  4.             /* what is the appropriate thing to do here on EBADF */  
  5.             if (errno == EINTR)  
  6.                 continue;   /* while(1) */  
  7.             else if (errno != EBADF) {  
  8.                 DIE("select");  
  9.             }  
  10.         }  
  11.   
  12.         time(&t_time);  
  13.         if (FD_ISSET(server_s, &block_read_fdset))  
  14.             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()之间客户关闭连接的情况:

  1. #ifdef DEBUGNONINET  
  2.     /* This shows up due to race conditions in some Linux kernels 
  3.        when the client closes the socket sometime between 
  4.        the select() and accept() syscalls. 
  5.        Code and description by Larry Doolittle <ldoolitt@boa.org> 
  6.      */  
  7. #define HEX(x) (((x)>9)?(('a'-10)+(x)):('0'+(x)))  
  8.     if (remote_addr.sin_family != AF_INET) {  
  9.         struct sockaddr *bogus = (struct sockaddr *) &remote_addr;  
  10.         char *ap, ablock[44];  
  11.         int i;  
  12.         close(fd);  
  13.         log_error_time();  
  14.         for (ap = ablock, i = 0; i < remote_addrlen && i < 14; i++) {  
  15.             *ap++ = ' ';  
  16.             *ap++ = HEX((bogus->sa_data[i] >> 4) & 0x0f);  
  17.             *ap++ = HEX(bogus->sa_data[i] & 0x0f);  
  18.         }  
  19.         *ap = '\0';  
  20.         fprintf(stderr, "non-INET connection attempt: socket %d, "  
  21.                 "sa_family = %hu, sa_data[%d] = %s\n",  
  22.                 fd, bogus->sa_family, remote_addrlen, ablock);  
  23.         return;  
  24.     }  
  25. #endif  

不过,我认为没必要。如果客户端在select()和accept()之间关闭连接的话,accept应该返回EAGAIN或EWOULDBLOCK错误,然后return了。

if这段代码看起来也只是简单的检测一下客户端的sin_family是否是AF_INET。并不像处理注释说明的情况。


然后又是一小段意义不明的代码:

  1. /* XXX Either delete this, or document why it's needed */  
  2. /* Pointed out 3-Oct-1999 by Paul Saab <paul@mu.org> */  
  3. #ifdef REUSE_EACH_CLIENT_CONNECTION_SOCKET  
  4.     if ((setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &sock_opt,  
  5.                     sizeof (sock_opt))) == -1) {  
  6.         DIE("setsockopt: unable to set SO_REUSEADDR");  
  7.     }  
  8. #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队列的请求了,并进行相应处理了。

首先检查是否有积压数据发送:

  1. if (current->buffer_end && /* there is data in the buffer */  
  2.             current->status != DEAD && current->status != DONE) {  
  3.             retval = req_flush(current);  
  4.             /* 
  5.              * retval can be -2=error, -1=blocked, or bytes left 
  6.              */  
  7.             if (retval == -2) { /* error */  
  8.                 current->status = DEAD;  
  9.                 retval = 0;  
  10.             } else if (retval >= 0) {  
  11.                 /* notice the >= which is different from below? 
  12.                    Here, we may just be flushing headers. 
  13.                    We don't want to return 0 because we are not DONE 
  14.                    or DEAD */  
  15.   
  16.                 retval = 1;  
  17.             }  
  18.         }  
如果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处理留到下次~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值