那谁的技术博客

感兴趣领域:高性能服务器编程,存储,算法,Linux内核
随笔 - 210, 文章 - 0, 评论 - 1183, 引用 - 0
数据加载中……

lighttpd1.4.18代码分析(八)--状态机(2)CON_STATE_READ状态

今天继续沿着状态转换序列图讲解状态机,这次到了CON_STATE_READ状态, 首先看看connection_state_machine函数部分的代码:

        // 读             
        case CON_STATE_READ_POST:
        
case CON_STATE_READ:
            
if (srv->srvconf.log_state_handling) {
                log_error_write(srv, __FILE__, __LINE__, 
"sds",
                        
"state for fd", con->fd, connection_get_state(con->state));
            }

            connection_handle_read_state(srv, con);
            
break;
可以看到,CON_STATE_READ_POST状态和CON_STATE_READ状态调用的同一段代码,不过我们今天讲解的CON_STATE_READ状态,CON_STATE_READ_POST状态在后面讲解.

上面的代码调用了
connection_handle_read_state函数,进入这个函数内部,分为几个部分进行分析:
    if (con->is_readable) {
        con
->read_idle_ts = srv->cur_ts;

        
// -1:出错 -2:对方关闭连接 0:成?
        switch(connection_handle_read(srv, con)) {
        
case -1:
            
return -1;
        
case -2:
            is_closed 
= 1;
            
break;
        
default:
            
break;
        }
    }
这里调用函数connection_handle_read, 来看看这个函数的实现:
// -1:出错 -2:对方关闭连接 0:成功
static int connection_handle_read(server *srv, connection *con) {
    
int len;
    buffer 
*b;
    
int toread;

    
if (con->conf.is_ssl) {
        
return connection_handle_read_ssl(srv, con);
    }

#if defined(__WIN32)
    b 
= chunkqueue_get_append_buffer(con->read_queue);
    buffer_prepare_copy(b, 
4 * 1024);
    len 
= recv(con->fd, b->ptr, b->size - 10);
#else
    
// 获取有多少数据可读
    if (ioctl(con->fd, FIONREAD, &toread)) {
        log_error_write(srv, __FILE__, __LINE__, 
"sd",
                
"unexpected end-of-file:",
                con
->fd);
        
return -1;
    }
    
// 根据数据量准备缓冲区
    b = chunkqueue_get_append_buffer(con->read_queue);
    buffer_prepare_copy(b, toread 
+ 1);
    
// 读数据
    len = read(con->fd, b->ptr, b->size - 1);
#endif

    
if (len < 0) {
        con
->is_readable = 0;

        
// Non-blocking  I/O has been selected using O_NONBLOCK and no data
        
// was immediately available for reading.
        if (errno == EAGAIN) 
            
return 0;
        
if (errno == EINTR) {
            
/* we have been interrupted before we could read */
            con
->is_readable = 1;
            
return 0;
        }

        
if (errno != ECONNRESET) {
            
/* expected for keep-alive */
            log_error_write(srv, __FILE__, __LINE__, 
"ssd""connection closed - read failed: ", strerror(errno), errno);
        }

        connection_set_state(srv, con, CON_STATE_ERROR);

        
return -1;
    } 
else if (len == 0) {
        
// 当读入数据 = 0时 表示对端关闭了连接
        con->is_readable = 0;
        
/* the other end close the connection -> KEEP-ALIVE */

        
/* pipelining */

        
return -2;
    } 
else if ((size_t)len < b->size - 1) {
        
/* we got less then expected, wait for the next fd-event */

        con
->is_readable = 0;
    }

    
// 记录读入的数据量 未使用的数据第一个字节为0
    b->used = len;
    b
->ptr[b->used++= '\0';

    con
->bytes_read += len;
#if 0
    dump_packet(b
->ptr, len);
#endif

    
return 0;
}
简单的说, 该函数首先调用ioctl获取fd对应的缓冲区中有多少可读的数据, 然后调用chunkqueue_get_append_buffer和buffer_prepare_copy函数准备好所需的缓冲区, 准备好缓冲区之后, 调用read函数读取缓冲区中的数据.对read函数的调用结果进行区分, 小于0表示出错, 返回-1;等于0表示关闭了连接, 返回-2;假如读取的数据长度比预期的小, 那么就等待下一次继续读数据, 最后将已经读入的数据缓冲区最后一个字节置'\0',返回0.

继续回到函数connection_handle_read_state中, 看接下来的代码:
    // 这一段循环代码用于更新read chunk队列,没有使用的chunk都归入未使用chunk链中
    /* the last chunk might be empty */
    
for (c = cq->first; c;) {
        
if (cq->first == c && c->mem->used == 0) {
            
// 如果第一个chunk是空的并且没有使用过
            /* the first node is empty */
            
/*  and it is empty, move it to unused */

            
// 则chunk队列的第一个chunk为下一个chunk
            cq->first = c->next;
            
// 第一个chunk为NULL, 那么最后一个chunk为NULL
            if (cq->first == NULL) 
                cq
->last = NULL;

            
// 更新chunk队列中对于未使用chunk的记录
            c->next = cq->unused;
            cq
->unused = c;
            cq
->unused_chunks++;

            
// 重新指向第一个chunk
            c = cq->first;
        } 
else if (c->next && c->next->mem->used == 0) {
            chunk 
*fc;
            
// 如果下一个chunk存在而且未使用过
            /* next node is the last one */
            
/*  and it is empty, move it to unused */

            
// 将这个chunk从队列中分离出去, 同时fc指向这个未使用的chunk
            fc = c->next;
            c
->next = fc->next;

            
// 将这个未使用的chunk(fc所指)保存到未使用chunk链中
            fc->next = cq->unused;
            cq
->unused = fc;
            cq
->unused_chunks++;

            
/* the last node was empty */
            
// 如果c的下一个chunk是空的, 那么chunk队列的最后一个chunk就是c了
            if (c->next == NULL) {
                cq
->last = c;
            }

            
// 继续往下走
            c = c->next;
        } 
else {
            
// 继续往下走
            c = c->next;
        }
    }
每个connection结构体中, 有一个read_queue成员, 该成员是chunkqueue类型的, 一个connection读入的数据都会保存在这个成员中, 由于一直没有详细介绍chunkqueue结构体及其使用, 这里不对上面的过程进行详细的分析, 只需要知道chunkqueue结构体内部使用的是链表保存数据, 上面这段代码遍历这个链表, 将未使用的部分抽取下来放到未使用chunkqueue中.

继续看下面的代码, 下面的代码根据状态是CON_STATE_READ还是CON_STATE_READ_POST进行了区分, 同样的,目前仅关注CON_STATE_READ状态部分:
    case CON_STATE_READ:    // 如果是可读状态
        /* if there is a \r\n\r\n in the chunkqueue
         *
         * scan the chunk-queue twice
         * 1. to find the \r\n\r\n
         * 2. to copy the header-packet
         *
         
*/

        last_chunk 
= NULL;
        last_offset 
= 0;

        
// 遍历read chunk队列
        for (c = cq->first; !last_chunk && c; c = c->next) {
            buffer b;
            size_t i;

            b.ptr 
= c->mem->ptr + c->offset;
            b.used 
= c->mem->used - c->offset;

            
// 遍历当前chunk中的每一个字符
            for (i = 0!last_chunk && i < b.used; i++) {
                
char ch = b.ptr[i];
                size_t have_chars 
= 0;

                
// 判断当前字符
                switch (ch) {
                
case '\r':        // 如果当前字符是'\r'
                    /* we have to do a 4 char lookup */
                    
// 该chunk还剩余多少个字符
                    have_chars = b.used - i - 1;
                    
                    
if (have_chars >= 4) {
                        
// 如果当前剩余字符大于等于4, 判断紧跟着的4个字符是不是"\r\n\r\n", 如果是就退出循环
                        /* all chars are in this buffer */

                        
if (0 == strncmp(b.ptr + i, "\r\n\r\n"4)) {
                            
/* found */
                            last_chunk 
= c;
                            last_offset 
= i + 4;

                            
break;
                        }
                    } 
else {
                        
// 否则就去查看下一个chunk, 看看是不是和这个chunk一起形成了"\r\n\r\n"
                        chunk *lookahead_chunk = c->next;
                        size_t missing_chars;
                        
/* looks like the following chars are not in the same chunk */

                        missing_chars 
= 4 - have_chars;

                        
if (lookahead_chunk && lookahead_chunk->type == MEM_CHUNK) {
                            
/* is the chunk long enough to contain the other chars ? */

                            
if (lookahead_chunk->mem->used > missing_chars) {
                                
if (0 == strncmp(b.ptr + i, "\r\n\r\n", have_chars) &&
                                    
0 == strncmp(lookahead_chunk->mem->ptr, "\r\n\r\n" + have_chars, missing_chars)) {

                                    last_chunk 
= lookahead_chunk;
                                    last_offset 
= missing_chars;

                                    
break;
                                }
                            } 
else {
                                
/* a splited \r \n */
                                
break;
                            }
                        }
                    }

                    
break;
                }
            }
        }
这段代码用于在读入数据中查找"\r\n\r\n",熟悉http协议的人知道, 这代表着一个http请求的结束,也就是说, 上面的代码用于判断是否已经接受了一个完整的http请求.但是有一个细节部分需要注意, 前面说过chunkqueue内部是使用一个链表来存放数据,比方说这个链表中有两个节点, 一个节点存放一字节的数据, 一个节点存放了十字节的数据,这时候可能会出现这样的情况:假如在一个节点存放的数据中找到了字符'\r',而该节点剩下的数据不足以存放"\r\n\r\n"字符串剩余的字符, 也就是说, 不足4个字节, 那么查找"\r\n\r\n"的过程就要延续到下一个节点继续进行查找.比如在一个节点中最后部分找到了"\r", 那么就要在下一个节点的数据起始位置中查找"\n\r\n".

继续看下面的代码:
        /* found */
        
// 读取到了请求的结尾, 现在将请求字符串放到request字段中
        if (last_chunk) {
            buffer_reset(con
->request.request);

            
for (c = cq->first; c; c = c->next) {
                buffer b;

                b.ptr 
= c->mem->ptr + c->offset;
                b.used 
= c->mem->used - c->offset;

                
if (c == last_chunk) {
                    b.used 
= last_offset + 1;
                }

                buffer_append_string_buffer(con
->request.request, &b);

                
if (c == last_chunk) {
                    c
->offset += last_offset;

                    
break;
                } 
else {
                    
/* the whole packet was copied */
                    c
->offset = c->mem->used - 1;
                }
            }

            
// 设置状态为读取请求结束
            connection_set_state(srv, con, CON_STATE_REQUEST_END);
        } 
else if (chunkqueue_length(cq) > 64 * 1024) {
            
// 读入的数据太多, 出错
            log_error_write(srv, __FILE__, __LINE__, "s""oversized request-header -> sending Status 414");

            con
->http_status = 414/* Request-URI too large */
            con
->keep_alive = 0;
            connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
        }
        
break;
如果前面查找到了"\r\n\r\n", 那么函数就进入这个部分.这部分代码做的事情就是复制http请求头到connection结构体中request成员中.需要注意的是如果没有查找到"\r\n\r\n",并且缓冲区数据长度大于64*1024, 也就是64K字节, 那么就返回414错误, 也就是说, 对于lighttpd而言, 一般的http请求不能超过64K字节.

这个过程就分析到这里,简单的总结一下:首先从缓冲区中读取数据, 然后查找"\r\n\r\n"字符串, 判断是否已经读取了完整的http请求, 如果是的话就复制下来, 最后进入CON_STATE_REQUEST_END状态, 这是下一节分析的内容.



posted on 2008-09-24 10:50 那谁 阅读(3402) 评论(1)  编辑 收藏 引用 所属分类: 网络编程服务器设计Linux/Unixlighttpd

评论

# re: lighttpd1.4.18代码分析(八)--状态机(2)CON_STATE_READ状态  回复  更多评论   

mark
2008-09-27 16:05 | 908971

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理