免费注册 查看新帖 |

Chinaunix

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

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

论坛徽章:
0
41 [报告]
发表于 2008-09-05 19:37 |只看该作者
原帖由 cjx_epop 于 2008-9-5 10:52 发表
各个大哥,我想问一下lighttpd1.4.18源码去哪下啊...

我也想学习学习。


google lighttpd
then download it

论坛徽章:
0
42 [报告]
发表于 2008-09-09 17:06 |只看该作者
跟着学习了,期待接下来的分析。。。

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

论坛徽章:
0
44 [报告]
发表于 2008-09-11 13:05 |只看该作者
分析的不错 等待下文

论坛徽章:
0
45 [报告]
发表于 2008-09-16 10:48 |只看该作者
楼主快点出下面的啊,等了很久了
顺便问一下
#define SET(x) \
    ev->x = fdevent_select_##x;

这种是什么用法,为什么不是
#define SET(x) \
    ev->x = fdevent_select_x;
而是在中间多了两个#号呢?
谢谢

论坛徽章:
0
46 [报告]
发表于 2008-09-16 10:53 |只看该作者
原帖由 boywonder 于 2008-9-16 10:48 发表
楼主快点出下面的啊,等了很久了
顺便问一下
#define SET(x) \
    ev->x = fdevent_select_##x;

这种是什么用法,为什么不是
#define SET(x) \
    ev->x = fdevent_select_x;
而是在中间多了 ...


不好意思,上周回家过中秋了,今天凌晨刚回来.

我的这个分析比较少涉及这么细节的地方,只是说大致的框架.
你的这个问题,可以去搜索一下C语言中的宏的使用方法,用##可以连接形成一个新的"符号".

论坛徽章:
0
47 [报告]
发表于 2008-09-16 11:03 |只看该作者

回复 #50 converse 的帖子

好的,我去查查。
你的文章我结合代码看了5、6遍了,现在看的有点眉目了,期待精彩继续:)

论坛徽章:
0
48 [报告]
发表于 2008-09-16 12:23 |只看该作者
前端时间想看apache代码  发现太复杂了

论坛徽章:
0
49 [报告]
发表于 2008-09-19 10:55 |只看该作者

lighttpd1.4.18代码分析(六)--处理连接fd的流程

lighttpd1.4.18代码分析(六)--处理连接fd的流程

现在开始讲解lighttpd如何处理连接fd.

在第四节,已经讲解了如何处理服务器负责监听连接的fd,处理连接fd的流程在前半部分与监听fd大体相同:在通过监听fd接收一个新的连接之后,服务器将这个fd加入到事件处理器中, 设置它所感兴趣的IO事件类型, 当IO状态发生变化时, 事件处理器获取被触发的fd,回调函数,事件等,然后通过已经注册的回调函数进行处理.

这里的区别就在于回调函数的不同上.对于监听fd而言, 该回调函数是network.c文件中的network_server_handle_fdevent函数,这个在第四节中已经做了分析;与之对应的,连接fd的回调函数是connections.c文件中的connection_handle_fdevent函数.


  1. // 这个函数是处理接受链接的函数, 与network_server_handle_fdevent对应
  2. handler_t connection_handle_fdevent(void *s, void *context, int revents) {
  3.     server     *srv = (server *)s;
  4.     connection *con = context;

  5.     // 添加到server的joblist中
  6.     joblist_append(srv, con);

  7.     // 可读
  8.     if (revents & FDEVENT_IN) {
  9.         con->is_readable = 1;
  10.     }
  11.    
  12.     // 可写
  13.     if (revents & FDEVENT_OUT) {
  14.         con->is_writable = 1;
  15.         /* we don't need the event twice */
  16.     }

  17.     // 既不可读也不可写, 可能是出错了
  18.     if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
  19.         /* looks like an error */

  20.         /* FIXME: revents = 0x19 still means that we should read from the queue */
  21.         if (revents & FDEVENT_HUP) {
  22.             if (con->state == CON_STATE_CLOSE) {
  23.                 con->close_timeout_ts = 0;
  24.             } else {
  25.                 /* sigio reports the wrong event here
  26.                  *
  27.                  * there was no HUP at all
  28.                  */
  29. #ifdef USE_LINUX_SIGIO
  30.                 if (srv->ev->in_sigio == 1) {
  31.                     log_error_write(srv, __FILE__, __LINE__, "sd",
  32.                         "connection closed: poll() -> HUP", con->fd);
  33.                 } else {
  34.                     connection_set_state(srv, con, CON_STATE_ERROR);
  35.                 }
  36. #else
  37.                 connection_set_state(srv, con, CON_STATE_ERROR);
  38. #endif

  39.             }
  40.         } else if (revents & FDEVENT_ERR) {
  41. #ifndef USE_LINUX_SIGIO
  42.             log_error_write(srv, __FILE__, __LINE__, "sd",
  43.                     "connection closed: poll() -> ERR", con->fd);
  44. #endif
  45.             connection_set_state(srv, con, CON_STATE_ERROR);
  46.         } else {
  47.             log_error_write(srv, __FILE__, __LINE__, "sd",
  48.                     "connection closed: poll() -> ???", revents);
  49.         }
  50.     }

  51.     // 如果连接的状态是READ, 那么处理read状态
  52.     if (con->state == CON_STATE_READ ||
  53.         con->state == CON_STATE_READ_POST) {
  54.         connection_handle_read_state(srv, con);
  55.     }

  56.     // 如果连接状态是WRITE并却写缓冲区队列中不为空 并且该连接是可写的, 就去处理写状态
  57.     if (con->state == CON_STATE_WRITE &&
  58.         !chunkqueue_is_empty(con->write_queue) &&
  59.         con->is_writable) {

  60.         if (-1 == connection_handle_write(srv, con)) {
  61.             connection_set_state(srv, con, CON_STATE_ERROR);

  62.             log_error_write(srv, __FILE__, __LINE__, "ds",
  63.                     con->fd,
  64.                     "handle write failed.");
  65.         } else if (con->state == CON_STATE_WRITE) {
  66.             con->write_request_ts = srv->cur_ts;
  67.         }
  68.     }

  69.     // 如果连接的状态是关闭
  70.     if (con->state == CON_STATE_CLOSE) {
  71.         /* flush the read buffers */
  72.         int b;

  73.         // 检查读缓冲区中是否还有数据
  74.         if (ioctl(con->fd, FIONREAD, &b)) {
  75.             log_error_write(srv, __FILE__, __LINE__, "ss",
  76.                     "ioctl() failed", strerror(errno));
  77.         }

  78.         // 如果还有数据, 打印错误log, 并且读入数据
  79.         if (b > 0) {
  80.             char buf[1024];
  81.             log_error_write(srv, __FILE__, __LINE__, "sdd",
  82.                     "CLOSE-read()", con->fd, b);

  83.             /* */
  84.             read(con->fd, buf, sizeof(buf));
  85.         } else {
  86.             /* nothing to read */

  87.             con->close_timeout_ts = 0;
  88.         }
  89.     }
  90.    
  91.     return HANDLER_FINISHED;
  92. }

复制代码


在结构体connection,也就是保存连接相关数据的结构体中, 有一个叫state的成员, 顾名思义, 这个成员保存的是一个连接的状态,这里所说的状态与前面提到的IO状态是不同, IO状态是用于表示一个fd可读/可写/出错等, 而这个状态更多的是与协议相关的部分.它是一个枚举类型:


  1. /* the order of the items should be the same as they are processed
  2. * read before write as we use this later */
  3. typedef enum {
  4.     CON_STATE_CONNECT,            // 连接
  5.     CON_STATE_REQUEST_START,    // 开始获取请求
  6.     CON_STATE_READ,                // 处理读
  7.     CON_STATE_REQUEST_END,        // 请求结束
  8.     CON_STATE_READ_POST,        // 处理读,但是是POST过来的数据
  9.     CON_STATE_HANDLE_REQUEST,    // 处理请求
  10.     CON_STATE_RESPONSE_START,    // 开始回复
  11.     CON_STATE_WRITE,            // 处理写
  12.     CON_STATE_RESPONSE_END,        // 回复结束
  13.     CON_STATE_ERROR,            // 出错
  14.     CON_STATE_CLOSE                // 连接关闭
  15. } connection_state_t;

复制代码


为什么需要这些状态?因为lighttpd中采用了所谓"状态机"去处理连接,而这些状态就是状态机中的各种不同状态.
在lighttpd的官方文档中, 对其使用的状态机有一篇文档,在这里:
http://trac.lighttpd.net/trac/wiki/Docs%3AInternalHTTPStates

我觉得里面的这幅图非常的直观,学习过编译原理的人一看就可以知道这是状态机的转换图:
[img] [/img]
现在回到本章的主题中, lighttpd如何处理连接fd.
前面给出的处理连接fd的回调函数,最开始地方有一段代码:
    joblist_append(srv, con);
这个函数将一个连接connection结构体放入到joblist中, 后面的部分根据不同的情况设置connection中的state字段,调用的是connection_set_state函数.

现在回到server.c函数中, 第四节中已经结合处理监听fd的流程讲解了这段函数:


  1.   // 轮询FD
  2.         if ((n = fdevent_poll(srv->ev, 1000)) > 0) {
  3.             /* n is the number of events */
  4.             int revents;
  5.             int fd_ndx;

  6.             fd_ndx = -1;
  7.             do {
  8.                 fdevent_handler handler;
  9.                 void *context;
  10.                 handler_t r;

  11.                 // 获得处理这些事件的函数指针 fd等

  12.                 // 获得下一个fd在fdarray中的索引
  13.                 fd_ndx  = fdevent_event_next_fdndx (srv->ev, fd_ndx);
  14.                 // 获得这个fd要处理的事件类型
  15.                 revents = fdevent_event_get_revent (srv->ev, fd_ndx);
  16.                 // 获取fd
  17.                 fd      = fdevent_event_get_fd     (srv->ev, fd_ndx);
  18.                 // 获取回调函数
  19.                 handler = fdevent_get_handler(srv->ev, fd);
  20.                 // 获取处理相关的context(对server是server_socket指针, 对client是connection指针)
  21.                 context = fdevent_get_context(srv->ev, fd);

  22.                 /* connection_handle_fdevent needs a joblist_append */
  23.                 // 进行处理
  24.                 switch (r = (*handler)(srv, context, revents)) {
  25.                 case HANDLER_FINISHED:
  26.                 case HANDLER_GO_ON:
  27.                 case HANDLER_WAIT_FOR_EVENT:
  28.                 case HANDLER_WAIT_FOR_FD:
  29.                     break;
  30.                 case HANDLER_ERROR:
  31.                     /* should never happen */
  32.                     SEGFAULT();
  33.                     break;
  34.                 default:
  35.                     log_error_write(srv, __FILE__, __LINE__, "d", r);
  36.                     break;
  37.                 }
  38.             } while (--n > 0);
  39.         } else if (n < 0 && errno != EINTR) {
  40.             log_error_write(srv, __FILE__, __LINE__, "ss",
  41.                     "fdevent_poll failed:",
  42.                     strerror(errno));
  43.         }

复制代码


在server.c文件中, 紧跟着这段代码的是:


  1.         // 处理joblist中的连接
  2.         for (ndx = 0; ndx < srv->joblist->used; ndx++) {
  3.             connection *con = srv->joblist->ptr[ndx];
  4.             handler_t r;

  5.             connection_state_machine(srv, con);

  6.             switch(r = plugins_call_handle_joblist(srv, con)) {
  7.             case HANDLER_FINISHED:
  8.             case HANDLER_GO_ON:
  9.                 break;
  10.             default:
  11.                 log_error_write(srv, __FILE__, __LINE__, "d", r);
  12.                 break;
  13.             }

  14.             con->in_joblist = 0;
  15.         }

复制代码


简单的说, 这段代码是一个循环, 从joblist中依次取出已经放在这里的connection指针, 再调用connection_state_machine函数进行处理.

connection_state_machine函数是一个非常重要的函数, 它就是处理连接fd的状态机, 在后面将详细分析这个函数.

现在回顾一下lighttpd处理连接fd的大体框架:前面的流程与处理监听fd大体相同,在与处理连接fd相关的回调函数中, 首先将需要处理的fd相关的connection加入到joblist中, 设置它的state, 后面再轮询joblist, 进入状态机进行处理.

这个过程可能值得商榷, 比如为什么在回调函数中需要将一个connection指针加入到joblist中, 后面再一个循环轮询joblist中的connection,这样不是显得效率低下吗?我们需要注意的是,前面提到的IO事件状态和connection中的成员state是不同的!第一个轮询过程(IO事件处理器的轮询)是根据哪些fd的IO发生了变化被触发而去调用回调函数, 而后面的循环(joblist的轮询)中的connection则不一定都是IO发生变化的!

打一个比方, 一个连接到来, 此时我们把它放入到事件处理器中, 当它可读时被触发, 也就是在第一个轮询中被触发;如果在处理的时候出了问题, 不能继续, 此时我们只需要保存它当前的state字段, 在下一次操作中, 由于它的IO没有发生变化, 那么将不会在第一个轮询也就是IO事件处理器中被处理, 而只会在轮询joblist时被处理, 只需要它的state字段是正确的, 放到状态机处理函数中就可以继续下去.

论坛徽章:
0
50 [报告]
发表于 2008-09-19 11:22 |只看该作者
终于等到了,接着看,呵呵。。。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP