Lighttpd启动时完成了一系列初始化操作后,就进入了一个包含11个状态的有限状态机中。

每个连接都是一个connection实例(con),状态的切换取决于con->state。

lighttpd经过初步处理后将con的基本信息初始化,而插件对事件的处理就是针对con进行的,它拿到con后按照业务需要进行相应处理,然后再交还给lighttpd,lighttpd根据con中的信息完成响应。

状态定义如下:

[cpp] view plain copy
  1. typedef enum
  2. {
  3. CON_STATE_CONNECT,             //connect 连接开始
  4. CON_STATE_REQUEST_START,     //reqstart 开始读取请求
  5. CON_STATE_READ,             //read 读取并解析请求
  6. CON_STATE_REQUEST_END,         //reqend 读取请求结束
  7. CON_STATE_READ_POST,         //readpost 读取post数据
  8. CON_STATE_HANDLE_REQUEST,     //handelreq 处理请求
  9. CON_STATE_RESPONSE_START,     //respstart 开始回复
  10. CON_STATE_WRITE,             //write 回复写数据
  11. CON_STATE_RESPONSE_END,     //respend 回复结束
  12. CON_STATE_ERROR,             //error 出错
  13. CON_STATE_CLOSE             //close 连接关闭
  14. } connection_state_t;

下面就是lighttpd的状态机:

在每个连接中都会保存这样一个状态机,用以表示当前连接的状态。

在连接建立以后,在connections.c/connection_accpet()函数中,lighttpd调用connection_set_state()函数,将新建立的连接的状态设置为CON_STATE_REQUEST_START。在这个状态中,lighttpd记录连接建立的时间等信息。

整个状态机的核心函数是connections.c/ connection_state_machine()函数。

函数的主体部分删减之后如下:

[cpp] view plain copy
  1. int connection_state_machine(server * srv, connection * con)
  2. {
  3. int done = 0, r;
  4. while (done == 0)
  5. {
  6. size_t ostate = con -> state;
  7. int b;
  8. //根据当前状态机的状态进行相应的处理和状态转换。
  9. switch (con->state)
  10. {
  11. case CON_STATE_REQUEST_START:    /* transient */
  12. //do something
  13. case CON_STATE_REQUEST_END:    /* transient */
  14. //do something
  15. case CON_STATE_HANDLE_REQUEST:
  16. //do something
  17. case CON_STATE_RESPONSE_START:
  18. //do something
  19. case CON_STATE_RESPONSE_END:    /* transient */
  20. //do something
  21. case CON_STATE_CONNECT:
  22. //do something
  23. case CON_STATE_CLOSE:
  24. //do something
  25. case CON_STATE_READ_POST:
  26. //do something
  27. case CON_STATE_READ:
  28. //do something
  29. case CON_STATE_WRITE:
  30. //do something
  31. case CON_STATE_ERROR:    /* transient */
  32. //do something
  33. default:
  34. //do something
  35. break;
  36. }//end of switch(con -> state) ...
  37. if (done == -1)
  38. {
  39. done = 0;
  40. }
  41. else if (ostate == con->state)
  42. {
  43. done = 1;
  44. }
  45. }
  46. /* something else */
  47. /* 将fd加入到fdevent系统中,等待IO事件。
  48. * 当有数据可读的时候,在main函数中,lighttpd调用这个fd对应的handle函数,
  49. * 这里就是connection_handle_fdevent()函数。
  50. * 这个函数一开始将连接加入到了joblist(作业队列)中。
  51. */
  52. switch (con->state)
  53. {
  54. case CON_STATE_READ_POST:
  55. case CON_STATE_READ:
  56. case CON_STATE_CLOSE:
  57. fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
  58. break;
  59. case CON_STATE_WRITE:
  60. /* request write-fdevent only if we really need it
  61. * - if we have data to write
  62. * - if the socket is not writable yet
  63. */
  64. if (!chunkqueue_is_empty(con->write_queue) &&
  65. (con->is_writable == 0)&& (con->traffic_limit_reached == 0))
  66. {
  67. fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
  68. }
  69. else
  70. {
  71. fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
  72. }
  73. break;
  74. default:
  75. fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
  76. break;
  77. }
  78. return 0;
  79. }

这个函数首先根据当前的状态进入对应的switch分支执行相应的动作,然后根据情况进入下一个状态。

跳出switch语句之后,如果连接的状态没有改变,说明连接读写数据还没有结束,但是需要等待IO事件,这时跳出循环,等待IO事件。

如果在处理的过程中不需要等待IO事件,那么在while循环中,连接将被处理完毕并关闭。

在我们的main函数中,之前讨论过,在一个while循环中,处理超时,处理IO时间,之后有下面这段代码:

[cpp] view plain copy
  1. for (ndx = 0; ndx < srv->joblist->used; ndx++) {
  2. connection *con = srv->joblist->ptr[ndx];
  3. handler_t r;
  4. connection_state_machine(srv, con);
  5. switch(r = plugins_call_handle_joblist(srv, con)) {
  6. case HANDLER_FINISHED:
  7. case HANDLER_GO_ON:
  8. break;
  9. default:
  10. log_error_write(srv, __FILE__, __LINE__, "d", r);
  11. break;
  12. }
  13. con->in_joblist = 0;
  14. }

这段代码对joblist中的所有连接依次调用connection_state_machine()函数进行处理。

下面说明下各状态的主要内容:

[cpp] view plain copy
  1. CON_STATE_CONNECT
  2. 清除待读取队列中的数据-chunkqueue_reset(con->read_queue);
  3. 置con->request_count = 0。(本次连接还未处理过请求)
  4. CON_STATE_REQUEST_START  /*transient */
  5. 记录事件起始时间;
  6. con->request_count++(一次长连接最多可以处理的请求数量是有限制的);
  7. 转移到CON_STATE_READ状态。
  8. CON_STATE_READ和CON_STATE_READ_POST
  9. connection_handle_read_state(srv,con);
  10. CON_STATE_REQUEST_END    /*transient */
  11. http_request_parse(srv, con);
  12. 解析请求,若是POST请求则转移到CON_STATE_READ_POST状态,
  13. 否则转移到CON_STATE_HANDLE_REQUEST状态。
  14. CON_STATE_HANDLE_REQUEST
  15. http_response_prepare(srv, con);
  16. 函数中调用
  17. handle_uri_raw;
  18. handle_uri_clean;
  19. handle_docroot;
  20. handle_physical;
  21. handle_subrequest_start;
  22. handle_subrequest。
  23. 如果函数返回了HANDLER_FINISHED,且con->mode!=DIRECT(事件已经被我们的业务插件接管),
  24. 则直接进入CON_STATE_RESPONSE_START。
  25. 否则lighttpd会做一些处理后再进入CON_STATE_RESPONSE_START状态。
  26. 如果函数返回了HANDLER_WAIT_FOR_FD或
  27. HANDLER_WAIT_FOR_EVENT,
  28. 状态依旧会停留在CON_STATE_HANDLE_REQUEST,等待事件或数据。
  29. 如果函数返回了HANDLER_ERROR,进入到CON_STATE_ERROR状态。
  30. CON_STATE_RESPONSE_START
  31. connection_handle_write_prepare(srv,con);
  32. CON_STATE_WRITE
  33. connection_handle_write(srv,con);
  34. CON_STATE_RESPONSE_END
  35. 调用插件的handle_request_done接口。
  36. 如果是长连接,重新回到CON_STATE_REQUEST_START;否则调用插件的handle_connection_close接口。
  37. 执行connection_close(srv, con);和connection_reset(srv, con);将连接关闭。
  38. CON_STATE_ERROR   /* transient */
  39. 调用插件handle_request_done;
  40. 调用插件handle_connection_close;
  41. 执行connection_close将连接关闭。
  42. CON_STATE_CLOSE
  43. connection_close(srv, con);将连接关闭。

以上是状态机的概况。

总览了状态机,我们知道状态机会针对相应的阶段对事件进行处理,那么状态机是如何处理这些事件的?

事实上,对于事件的处理,一部分是由lighttpd完成的,而一部分是由插件完成的。插件中那些负责事件处理的接口分布在某几个状态中。我们只需在插件的各个阶段完成指定工作并返回相应的返回值,就可以促使状态机完成状态切换,完成事件的整套处理流程,并最终由lighttpd完成事件的响应。

在插件中,我们可以编写代码来注册lighttpd提供的回调接口,lighttpd在初始化阶段、状态机执行阶段、退出阶段会分别调用这些回调函数,完成插件的实例化,初始化,连接重置,事件处理,插件释放等功能。

要了解lighttpd对插件的调用方式,需要明白一个概念:事件接管。

对于每个事件,都有一个mode字段(con->mode)。该字段的定义:

typedef enum { DIRECT, EXTERNAL } connection_type;

连接对象有一个字段mode用来标识该连接是最初由服务器accept产生的客户端连接还是插件产生的其他辅助连接,当mode=DIRECT时表示对应连接由lighttpd服务器accept产生,mode!=DIRECT时表示对应连接是由插件产生的。

事件(con)初始化时mode是DIRECT;connection_reset(srv,con);

lighttpd在大部分流程中会在入口检查到mode != DIRECT时直接返回GO_ON。即:此事件由用户插件接管,lighttpd不参与。

用户编写的插件应通过将mode置为插件自身的ID达到接管的作用。插件ID是在插件加载时由插件的加载顺序确定的,是插件的唯一标识。

用户编写插件在每个接口的一开始应该判断mode是否等于自身的ID,若相等才能继续执行,否则直接退出,返回GO_ON。

了解了以上概念之后,我们就可以理解lighttpd对插件的调用方式了:

在lighttpd需要调用插件某一个阶段的接口函数时,会对所有插件注册在该处的接口顺序调用,顺序与插件加载顺序相同。例如:调用uri_raw接口,会先调用A插件的mod_A_uri_raw,然后调用B插件的mod_B_uri_raw,直到将所有已加载插件这个位置的接口全部调用完成。但实际处理这次事件通常只有一个插件,即插件ID与mode相同的那个插件。

因此,假设在CON_STATE_HANDLE_REQUEST状态,lighttpd调用了插件的handle_uri_raw接口,但是我们有多个插件,每个插件都注册了handle_uri_raw这个接口,lighttpd也能辨别出要使用哪个插件。

如果插件在处理事件的过程中,想让lighttpd接管,还需要把mode置为DIRECT才行。

以上是lighttpd状态机和插件的总览概况。

Lighttpd源码分析之状态机与插件相关推荐

  1. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  2. MyBatis源码分析(一)MyBatis整体架构分析

    文章目录 系列文章索引 一.为什么要用MyBatis 1.原始JDBC的痛点 2.Hibernate 和 JPA 3.MyBatis的特点 4.MyBatis整体架构 5.MyBatis主要组件及其相 ...

  3. 【Android 插件化】VirtualApp 源码分析 ( 启动应用源码分析 | HomePresenterImpl 启动应用方法 | VirtualCore 启动插件应用最终方法 )

    文章目录 一.启动应用源码分析 1.HomeActivity 启动应用点击方法 2.HomePresenterImpl 启动应用方法 3.VirtualCore 启动插件应用最终方法 一.启动应用源码 ...

  4. 【Android 插件化】VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )

    文章目录 一.目前的 API 现状 二.安装应用源码分析 1.安装按钮执行的操作 2.返回到 HomeActivity 执行的操作 一.目前的 API 现状 下图是 VirtualApp 官方给出的集 ...

  5. 【转载】SharpDevelop源码分析(四)SharpDevelop的AddInTree View 插件

    SharpDevelop的AddInTree View 插件 http://www.cnblogs.com/passos/archive/2004/10/15/52513.html 自从SharpDe ...

  6. Kafka源码分析10:副本状态机ReplicaStateMachine详解 (图解+秒懂+史上最全)

    文章很长,建议收藏起来,慢慢读! Java 高并发 发烧友社群:疯狂创客圈 奉上以下珍贵的学习资源: 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 + 大厂必备 +涨薪 ...

  7. 云客Drupal源码分析之插件系统(下)

    以下内容仅是一个预览,完整内容请见文尾: 至此本系列对插件的介绍全部完成,涵盖了系统插件的所有知识 全文目录(全文10476字): 实例化插件 插件映射Plugin mapping 插件上下文   具 ...

  8. 云客Drupal源码分析之插件系统(上)

    各位<云客drupal源码分析>系列的读者: 本系列一直以每周一篇的速度进行博客原创更新,希望帮助大家理解drupal底层原理,并缩短学习时间,但自<插件系统(上)>主题开始博 ...

  9. Android 双开沙箱 VirtualApp 源码分析(四)启动插件 Service

    上一章:Android 双开沙箱 VirtualApp 源码分析(三)App 启动 原生 Service 创建过程 首先有必要了解一下原生 framework 对 Service 的创建,因为在 VA ...

最新文章

  1. [置顶] WindowsPhone之我见
  2. java发送加密报文_RSA加密---从后台到客户端实现报文加解密
  3. 开源资产管理系统_开源cmdb来啦 通用CMDB 开源资产管理系统
  4. 在计算机领域做研究的一些想法-- 转载
  5. C++ cin.putback()输入【已知行数】但【未知每行数字个数】的思路
  6. linuxpython拍照_linux下python抓屏实现方法 -电脑资料
  7. LeetCode 660. 移除 9(9进制)
  8. 学习Spring Boot:(十九)Shiro 中使用缓存
  9. java调用lua 路径_luacom打开中文路径的Word文件
  10. git进阶(撤销pull、撤销merge、撤销add)
  11. React后台管理系统-订单管理
  12. python 超像素分割
  13. 【气象检测项目】BME280
  14. 博世中国的战略与战术,如何应对复杂多变的中国市场需求
  15. stub,存根是什么?
  16. Redis 使用lua脚本最全教程
  17. Excel 数据统计小技巧
  18. CTF-Crypto学习1(软件加壳、反汇编、Babe64、Rijndael密码算法)
  19. 贷超、贷超分销系统 贷超分销模式 简介
  20. MySQL攻略(2)

热门文章

  1. 第四天2017/03/31(上午:指针、数组的小知识)
  2. VS调用matlab
  3. Activiti最全入门教程
  4. 干掉状态:从session到token
  5. elasticsearch 第一篇(入门篇)
  6. Java 自动装箱性能
  7. LaTeX在双栏模式下插入跨栏图表
  8. 国内大学毕业论文LaTeX模板集合
  9. OpenCV-Python教程(6)(7)(8): Sobel算子 Laplacian算子 Canny边缘检测
  10. 鸟哥的Linux私房菜(基础篇)- 第十八章、认识系统服务 (daemons)