免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: converse
打印 上一主题 下一主题

lighttpd1.4.18代码分析 [复制链接]

论坛徽章:
0
61 [报告]
发表于 2008-09-24 10:54 |只看该作者

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

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

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

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

  8.             connection_handle_read_state(srv, con);
  9.             break;
复制代码


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

上面的代码调用了connection_handle_read_state函数,进入这个函数内部,分为几个部分进行分析:


  1. if (con->is_readable) {
  2.         con->read_idle_ts = srv->cur_ts;

  3.         // -1:出错 -2:对方关闭连接 0:成?
  4.         switch(connection_handle_read(srv, con)) {
  5.         case -1:
  6.             return -1;
  7.         case -2:
  8.             is_closed = 1;
  9.             break;
  10.         default:
  11.             break;
  12.         }
  13.     }
复制代码


这里调用函数connection_handle_read, 来看看这个函数的实现:

  1. // -1:出错 -2:对方关闭连接 0:成功
  2. static int connection_handle_read(server *srv, connection *con) {
  3.     int len;
  4.     buffer *b;
  5.     int toread;

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

  9. #if defined(__WIN32)
  10.     b = chunkqueue_get_append_buffer(con->read_queue);
  11.     buffer_prepare_copy(b, 4 * 1024);
  12.     len = recv(con->fd, b->ptr, b->size - 1, 0);
  13. #else
  14.     // 获取有多少数据可读
  15.     if (ioctl(con->fd, FIONREAD, &toread)) {
  16.         log_error_write(srv, __FILE__, __LINE__, "sd",
  17.                 "unexpected end-of-file:",
  18.                 con->fd);
  19.         return -1;
  20.     }
  21.     // 根据数据量准备缓冲区
  22.     b = chunkqueue_get_append_buffer(con->read_queue);
  23.     buffer_prepare_copy(b, toread + 1);
  24.     // 读数据
  25.     len = read(con->fd, b->ptr, b->size - 1);
  26. #endif

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

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

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

  42.         connection_set_state(srv, con, CON_STATE_ERROR);

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

  48.         /* pipelining */

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

  52.         con->is_readable = 0;
  53.     }

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

  57.     con->bytes_read += len;
  58. #if 0
  59.     dump_packet(b->ptr, len);
  60. #endif

  61.     return 0;
  62. }
复制代码


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

继续回到函数connection_handle_read_state中, 看接下来的代码:

  1. // 这一段循环代码用于更新read chunk队列,没有使用的chunk都归入未使用chunk链中
  2.     /* the last chunk might be empty */
  3.     for (c = cq->first; c;) {
  4.         if (cq->first == c && c->mem->used == 0) {
  5.             // 如果第一个chunk是空的并且没有使用过
  6.             /* the first node is empty */
  7.             /*  and it is empty, move it to unused */

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

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

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

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

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

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

  36.             // 继续往下走
  37.             c = c->next;
  38.         } else {
  39.             // 继续往下走
  40.             c = c->next;
  41.         }
  42.     }
复制代码


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

继续看下面的代码, 下面的代码根据状态是CON_STATE_READ还是CON_STATE_READ_POST进行了区分, 同样的,目前仅关注CON_STATE_READ状态部分:

  1.   case CON_STATE_READ:    // 如果是可读状态
  2.         /* if there is a \r\n\r\n in the chunkqueue
  3.          *
  4.          * scan the chunk-queue twice
  5.          * 1. to find the \r\n\r\n
  6.          * 2. to copy the header-packet
  7.          *
  8.          */

  9.         last_chunk = NULL;
  10.         last_offset = 0;

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

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

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

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

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

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

  42.                         missing_chars = 4 - have_chars;

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

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

  48.                                     last_chunk = lookahead_chunk;
  49.                                     last_offset = missing_chars;

  50.                                     break;
  51.                                 }
  52.                             } else {
  53.                                 /* a splited \r \n */
  54.                                 break;
  55.                             }
  56.                         }
  57.                     }

  58.                     break;
  59.                 }
  60.             }
  61.         }
复制代码

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

继续看下面的代码:


  1.        /* found */
  2.         // 读取到了请求的结尾, 现在将请求字符串放到request字段中
  3.         if (last_chunk) {
  4.             buffer_reset(con->request.request);

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

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

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

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

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

  15.                     break;
  16.                 } else {
  17.                     /* the whole packet was copied */
  18.                     c->offset = c->mem->used - 1;
  19.                 }
  20.             }

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

  26.             con->http_status = 414; /* Request-URI too large */
  27.             con->keep_alive = 0;
  28.             connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  29.         }
  30.         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状态, 这是下一节分析的内容.

论坛徽章:
0
62 [报告]
发表于 2008-09-24 16:31 |只看该作者
原帖由 converse 于 2008-9-24 10:30 发表
抱歉,不会用kqueue,因为我没用过BSD,不能给你什么提示了.


今天下午测试了一下kqueue,看到man里说它不能被fork所继承,仔细看了一下代码大概明白了一点,它是在init中关闭,然后在reset中再重新创建。

论坛徽章:
0
63 [报告]
发表于 2008-09-25 11:24 |只看该作者
mark
支持楼主

论坛徽章:
0
64 [报告]
发表于 2008-09-25 14:39 |只看该作者

回复 #62 converse 的帖子

看到这里是否要对http协议了解一下会好懂一些?如果要看的话是不是就看http的RFC就可以了?
之前稍微看过一些rfc文档,看的云里雾里的,都不知道里面在讲什么

[ 本帖最后由 boywonder 于 2008-9-25 14:43 编辑 ]

论坛徽章:
0
65 [报告]
发表于 2008-09-25 16:07 |只看该作者

回复 #65 boywonder 的帖子

应该有一点http协议的基础,其实我也不是特别熟悉http协议,都是一边看代码一边看协议.

论坛徽章:
0
66 [报告]
发表于 2008-09-25 16:30 |只看该作者
关注。。。学习

论坛徽章:
0
67 [报告]
发表于 2008-09-27 10:18 |只看该作者
分析得太棒了!!!!
学习..

论坛徽章:
0
68 [报告]
发表于 2008-09-27 12:22 |只看该作者
keyvalue.c 中有个 keyvalue_buffer, 用 grep 没查到定义的地方...

论坛徽章:
0
69 [报告]
发表于 2008-09-27 12:27 |只看该作者
原帖由 libin1983 于 2008-9-27 12:22 发表
keyvalue.c 中有个 keyvalue_buffer, 用 grep 没查到定义的地方...

找到了:

  1. #define KVB(x) \
  2. typedef struct {\
  3.         x **kv; \
  4.         size_t used;\
  5.         size_t size;\
  6. } x ## _buffer

  7. KVB(keyvalue);
  8. KVB(s_keyvalue);
  9. KVB(httpauth_keyvalue);
  10. KVB(pcre_keyvalue);
复制代码


KVB(keyvalue);
展开变成了:

  1. typedef struct {\
  2.         keyvalue **kv; \
  3.         size_t used;\
  4.         size_t size;\
  5. } keyvalue_buffer
复制代码

论坛徽章:
0
70 [报告]
发表于 2008-09-27 12:34 |只看该作者

回复 #71 libin1983 的帖子

这又是因为lighttpd中大量使用宏,造成了阅读上的障碍.它这一点,我挺不认同的.
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP