免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 36364 | 回复: 106
打印 上一主题 下一主题

[C++] [如何用状态机思想降低Server端开发复杂度? (GIT分支提供http server的实现框架示例) [复制链接]

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-10-20 07:52 |只看该作者 |倒序浏览
本帖最后由 linux_c_py_php 于 2012-12-02 19:56 编辑

代码最新说明:
@2012/10/22 18:42: 代码整理划分模块, 添加plugin回调逻辑, 提供简单的示例plugin demo, 供感兴趣的同学阅读与反馈.
@2012/10/22 18:43: 接下来会提供一个模仿Mysql的plugin demo, 向大家演示如何在框架基础上异步化阻塞操作. (本周内有空完成)
@2012/10/23 15:00: 添加Mysql Plugin, 终于给大家提供了一个生动的例子, 因为引入多Plugin, src/client.cpp, src/server.cpp中的Plugin调用逻辑出现了变动, 已经注释原因, 现在整个项目更具有可读性与实用性了, 欢迎大家找BUG提建议.
@2012/10/25 10:46:fake_mysql添加线程池, 更好的展现异步高并发特性.
@2012/10/26  23:00 Server支持HTTP协议啦~~~~~~~~~~
@2012/10/26 23:00 Server初步支持HTTP协议

@2012/10/27 10:44 Client延迟释放逻辑的已经完成, 这是一个现存的隐蔽BUG, 与多线程插件相关, 通过为Client事件回调逻辑添加一处状态检查以便等待所有插件完成工作后释放Client, 解决了该隐蔽复杂的逻辑BUG(欲知BUG细节可以联系我).

@2012/10/27 15:18 Client完善Http 500应答逻辑, 如果插件在ON_RESPONSE期间返回ERROR, 则向客户端返回500错误应答,为简化此实现,状态机新增BEFORE_ERROR, ON_ERROR状态以便实现500应答逻辑.

@2012/10/27 0:16 添加HttpResponse结构体, 替换原先std::string m_response, 添加相应序列化方法. 因此, 所有插件需调整代码, 直接操作HttpResponse结构体, 最终由主框架负责序列化后送出.

@2012/10/28 11:10: 代码趋向于稳定, 接下来将会主要做代码优化整理,包括添加中文注释等。

接下来将进行HttpResponse, HttpRequest结构体的API开发,以便让Plugin开发者可以方便的操作HTTP请求与应答,比如提供方便的获取GET参数的API,以及组织x-www-form表单POST数据拆解以便快速的访问key:val等等.

另外说明, Server主要是做应用逻辑的,基于HTTP协议,所以并不打算直接响应静态文件请求,所有逻辑交由plugin处理,我们是完全有能力开发一个静态文件plugin的,而且是很简单的. 并且, 接下来将会做几个有意思的插件, 比如统计Referer跳转以便统计本开源项目的关注人群是从哪个论坛跳转而来, 比如开发插件统计每个IP的访问次数以及user-agent等信息统计,再比如开发一个静态文件插件等等, 希望有同学可以在理解项目代码的基础上开发自己的插件, 提出开发过程中觉得不爽的地方, 有利于项目改进.

项目文件已更新为"HTTP版本", 更多说明围观git更新.

Github地址:https://github.com/liangdong/Server
https://github.com/liangdong/Server/tree/Http_Server

代码不啰嗦, 总代码量1300行, 适合阅读学习.

更多关于项目更新细节的记录均在2楼记录, 欢迎追踪.

Server自带两个Plugin:
1 ) slow_query计时plugin, 将会在结果中包含请求时间, 该plugin自身将会每5秒响应一次.
2 ) fake_mysql伪装mysql query plugin, 任何请求将会阻塞5秒后才返回Mysql查询结果(伪装非常慢的Mysql查询请求).

How to use ?

1, tar -zxvf libevent.tar.gz 解压
2, make -C plugin/slow_query && make -C plugin/fake_mysql 编译两个Plugin
3, make 编译Server
4, ./server 运行

How to test ?

1, telnet localhost 10000
2, 输入你的请求, 回车结束.(可以想办法欺负Server, 使劲敲回车, 等待Server应答, Server以回车符进行拆包).



=====================================================================================================

背景:
      XX希望用Epoll开发Server, 但业务上有很多阻塞的逻辑, 怕会阻塞Epoll, 所以不知道怎么设计比较好, 也就是知道大概要怎样, 但实际操作上又举步维艰的一种状态.
      
      比如, 老大交给他几个任务:
      1, 需要将Server接受到的请求向其他Server做转发并从其他Server读取应答后发回给Client(像反向代理一样).
      2, 需要将Client请求存入数据库, 或者从数据库中查询一些东西后返回给Client.

      XX疑惑的是, 他希望用epoll单线程, 而又不希望业务逻辑阻塞线程. 他大概知道要将业务逻辑放到线程中去阻塞的处理, 但不知道怎么融合到epoll单线程中, 有些不知所措了.

说明:
     我隔天写了一段代码, 只写了一些必要的内容, 让XX借着代码理解一下实现方式. 其实和一般的I/O复用Server开发是基本一样的, 但为了解决XX的问题, 引入状态机机制会让整个Server看起来更清晰易懂, 让Server架构和具体的业务逻辑解耦开来, 真正开发时只需要关注自己的业务逻辑, 这是非常有益处的, 同时, 也为XX解开了他最初的疑惑.

     
提示:
    基于write事件来完成轮询检查, 将业务阻塞逻辑移到独立线程处理, 用户请求在主线程与业务线程间异步传输, 是整个框架的核心思路, 不复杂, 但希望给大家学会这一点是如何简单实现的是很重要的内容.

    对于一部分质疑, 可以这么说, 万丈高楼平地起, 一个Server做好了才能做一群Server, 所以关注微架构设计是很必要的, 所以我希望大家讨论的内容是围绕核心问题的, 非常欢迎.



liangdong-Server-ecd1e7d.zip

17.66 KB, 下载次数: 232

评分

参与人数 2可用积分 +16 收起 理由
send_linux + 6 感谢原创!
lenky0401 + 10 很给力!

查看全部评分

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
2 [报告]
发表于 2012-10-20 07:54 |只看该作者
本帖最后由 linux_c_py_php 于 2012-10-25 10:50 编辑

帖子的重要回复 集中在这里:

1,
@all, 今天上午抽空折腾出来了Mysql Plugin, 该模块以独立线程形式与异步框架进行纯异步交互, 展示了一个非常生动且实际的Demo, 非常有学习价值. 因为引入多模块以及线程模块后, 引入了一些逻辑问题已经修正并在源代码基础上注释, 基本保障了基础框架代码没有变动, 为Plugin开发做好了一定的兼容.

代码已更新到1楼, 编译使用方法在1楼.


2,
框架目的是希望让其他开发参与者只开发Plugin/Module, 制作一个so, so里要求提供一个init函数, 主框架传入plugin结构体, 其他程序员实现的Init函数要把各个回调函数注册到plugin结构体上以便主框架以后调用.(在C++里我可以提供一个Interface类, 其他程序员可以继承Interface类实现所有回调, 并new一个对象返回到我的框架, 回调的说法是C里的概念)

3,
一方面状态机值得使用, 可以看到它显著的简化效果, 以前开发Server心里没有这个概念或者还没玩透根本没法实践, 结果每次写一个项目就是为这个项目现编现造一套新的设计, 而且对于阻塞的东西, 整个架构就直接多线程了, 基于状态机实现感觉像是流水线化了, 写10个Server也许10个Server都一样, 所以可以抽象出一个通用的框架.

另一方面, 你说的完全正确, webserver其实就是自称多进程单线程的master-worker架构, 但实际上把异步化的工作交给了plugin自己去保证, 实际上就算无良程序员写了一个module是阻塞逻辑的挂到nginx下, nginx也只能自认倒霉。

4,
@2012/10/25 10:46:fake_mysql添加线程池, 更好的展现异步高并发特性.

论坛徽章:
36
子鼠
日期:2013-08-28 22:23:29黄金圣斗士
日期:2015-12-01 11:37:51程序设计版块每日发帖之星
日期:2015-12-14 06:20:00CU十四周年纪念徽章
日期:2015-12-22 16:50:40IT运维版块每日发帖之星
日期:2016-01-25 06:20:0015-16赛季CBA联赛之深圳
日期:2016-01-27 10:31:172016猴年福章徽章
日期:2016-02-18 15:30:3415-16赛季CBA联赛之福建
日期:2016-04-07 11:25:2215-16赛季CBA联赛之青岛
日期:2016-04-29 18:02:5915-16赛季CBA联赛之北控
日期:2016-06-20 17:38:50技术图书徽章
日期:2016-07-19 13:54:03程序设计版块每日发帖之星
日期:2016-08-21 06:20:00
3 [报告]
发表于 2012-10-20 10:20 |只看该作者
这大清早的,顶你。

论坛徽章:
11
摩羯座
日期:2013-09-16 11:10:272015亚冠之阿尔萨德
日期:2015-06-12 22:53:29午马
日期:2014-04-15 11:08:53亥猪
日期:2014-03-02 23:46:35申猴
日期:2013-12-06 22:07:00亥猪
日期:2013-11-28 12:03:13双鱼座
日期:2013-11-21 14:43:56亥猪
日期:2013-10-23 10:55:49处女座
日期:2013-10-17 18:15:43午马
日期:2013-09-27 17:40:4215-16赛季CBA联赛之青岛
日期:2016-06-22 00:45:55
4 [报告]
发表于 2012-10-20 13:11 |只看该作者
强烈顶LCPP大虾!!

论坛徽章:
11
摩羯座
日期:2013-09-16 11:10:272015亚冠之阿尔萨德
日期:2015-06-12 22:53:29午马
日期:2014-04-15 11:08:53亥猪
日期:2014-03-02 23:46:35申猴
日期:2013-12-06 22:07:00亥猪
日期:2013-11-28 12:03:13双鱼座
日期:2013-11-21 14:43:56亥猪
日期:2013-10-23 10:55:49处女座
日期:2013-10-17 18:15:43午马
日期:2013-09-27 17:40:4215-16赛季CBA联赛之青岛
日期:2016-06-22 00:45:55
5 [报告]
发表于 2012-10-20 13:20 |只看该作者
是个法帖,值得研习咀嚼



论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
6 [报告]
发表于 2012-10-20 19:49 |只看该作者
本帖最后由 linux_c_py_php 于 2012-10-23 15:22 编辑

内容请见2楼.

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
7 [报告]
发表于 2012-10-20 22:07 |只看该作者
本帖最后由 zylthinking 于 2012-10-20 22:07 编辑

其实说了半天, 比较困难解决的恐怕就是:

还需要做mysql读写操作(众所周知mysql只有阻塞接口, 就连大名鼎鼎的jabberd2都让mysql操作直接阻塞在event loop里


既然开名宗义说是异步框架, 而且看样子是想解决
这些逻辑如果不是放在一个线程里阻塞的慢慢去做, 他实在不知道怎么办


也就是说用单线程。


我就好奇怎么解决这个问题, 草草扫了一下代码, 没看到, 通过所谓状态机, 想不出什么解决的手段, 要仔细推敲又要先学libevent, 懒得学。  干脆直接回答一下: 既然 mysql 是阻塞的, 那一旦调用进去, 不是你想出来就能出的来的, 既然如此, 如何做到一个所谓异步并且能够及时处理请求的服务器的? 或者说, mysql 阻塞了你的服务线程, 你怎么及时相应请求? 在单线程的情况下

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
8 [报告]
发表于 2012-10-20 23:06 |只看该作者
本帖最后由 linux_c_py_php 于 2012-10-20 23:11 编辑

我就知道会有楼上这种问题, 所以我特意在代码里注明了Plugin这个东西, 我简单说一下, 如果你真的不会设计, 我可以给一段如何异步化Mysql的例子.

这是基于我阅读Lighttpd, Nginx很久很久之后(多少思考, 多少开源代码阅读量, 一般人也许不懂), 结合工作中的类似Server框架需求的个人开发经验, 才能做到有能力设计各种Server端, 任何井底之蛙的质疑, 请注意提问方式和素质, 我不希望被人身攻击.

我只能说, 如果读者用心来思考本帖, 一定是终身受益, 如果内心抵触或者不服, 错过了精彩内容只能说遗憾了.

========================

首先, 代码里任何一个请求, 你会发现它被设置了5秒后应答, 我本意是模拟一个慢查询操作, 但我知道这个例子并不明显, 因为看起来根本没有做什么事情, 就是轮了几次而已, 等到5秒后完成应答, 在一般人看来, 肯定是这样的, 如果换成Mysql查询, 你肯定觉得我的状态机什么都没有解决, 这就是问题的核心, 你仍旧不知道怎么异步化而且不影响event loop.

我相信, 如果你是抱着谦虚的学习心态认真读代码, 至少你对状态机有了基础的认识, 并且我希望大家能够注意到的是代码里的这一句:

//模拟一个plugin的慢速处理(plugin自身已保证异步)


请注意, Plugin本身需保证异步.

我怕例子本身没有真正的去做一个plugin(比如Mysql查询), 大家仍然体会不到异步化是如何做到的, 所以特别说明: Plugin自身保证异步化, 就像Lighttpd/Nginx的Proxy Plugin, 或者Mysql基本认证等Plugin, 它们都是在ON_RESPONSE阶段针对该Client回调的, 但绝对是不会阻塞Event loop的, 如何做到这一点, 我相信是你所疑惑的.

直接说方法, 讲Mysql如何在上面代码基础上异步.

1, Server启动后, 将所有Plugin的一系列回调函数注册到Server, 之后调用所有Plugin的create callback回调以便注册每个plugin在整个Server运行过程中所需的信息与数据. 假设这里是mysql plugin, 那么要做的是创建1个线程, 创建2个队列, 创建2个pipe, 注册其中一个pipe[0]到event loop上监听可读(当然注册到event loop需要提供一个callback供libevent回调), 而线程则监听另一个pipe[0]监听可读. 有什么用呢? 继续往下读.
2, Client被Accept后, 立即为Client调用每个plugin的init callback回调为该client创建每个plugin在处理请求应答过程中需要的一些信息与数据.
3, Client进入ON_RESPONSE状态后, 开始逐个回调每个plugin的on request callback回调, 开始处理请求, 假设我们用的mysql这个plugin来获取一些数据. 什么过程呢?
先说插件的回调方式, 假设这里有2个插件, 那么除非第一个插件的on request callback返回OK, 否则是不会调用第二个plugin的 on request callback的, 这是一个串行的过程, 如果第一个插件返回NOT OK, 那么没问题, 就像上面代码没到5秒一样, 继续干其他事去, 不需要等.

再说Mysql怎么运作, 我们知道第2步为该client初始化了每个plugin的一些数据结构和信息, 所以回调on request callback的时候, mysql plugin的callback可以维护client里设置的plugin需要用到的结构体和信息, 比如里面记录着该client是否完成了本次Mysql查询, 如果完成了, 那么会设置对应的字段为OK, 并让callback返回OK, 以便框架继续调用下一个plugin, 这就是一个基本原理. 实现呢?

Client进入ON_REQUEST, 回调Mysql on_request_callback, 在callback里会先检查client->mysql_plugin_data->status是否为OK, 如果是, 那么直接返回即可, 因为这表明之前已经完成了Mysql查询. 如果是NOT_OK, 那么就需要开始Mysql请求了, 但不能在callback里就查, 会阻塞event loop, 所以前面create plugin时候创建的线程起作用了, 首先向队列1中丢一个task, 里面起码记录着task所属的client, 然后向线程监听的pipe的pipe[1](线程随便怎么监听, 与event loop框架无关)写1字节通知线程, 然后callback就可以返回NOT_OK了, event_loop继续跑其他的事.

线程里检测到pipe[0]后会读pipe[0]1字节, 然后从队列里取出一个task, 开始查询task请求的Mysql语句, 不管多慢都无所谓, 不影响event loop, 反正客户端等得起就OK.
线程查询完成, 将task包括上查询结果, 丢到通往event loop的队列里, 并向通往event loop监听的pipe[1]写1字节触发 ,这样一个Mysql请求就从event loop走到mysql thread, 最终走回了event loop.

event loop监听的pipe[0]触发可读事件, 回调注册到libevent的一个callback, 该函数读pipe[0]1字节,从队列中取出包含结果的task结构体, 取出task中引用的client对象, 设置client->mysql_plugin_data->status = FINISH; 并将查询结果也放到client->mysql_plugin_data->data里, 然后这个callback就可以返回了.

在接下来client的write事件callback中, 处于ON_RESPONSE状态的client回调到Mysql plugin时, mysql plulgin的on_request_callback会检测到finish, 所以将Mysql查询结果从clieng->mysql_plugin_data中取出来与response做结合就可以设置status=OK返回OK了, 接下来其他plugin走同样的过程, 最终response被送出, client进入新的状态.


有时间, 我把Mysql描述的这个流程写成代码给大家参考吧.

论坛徽章:
5
技术图书徽章
日期:2013-08-17 07:26:49双子座
日期:2013-09-15 16:46:29双子座
日期:2013-09-25 08:17:09技术图书徽章
日期:2013-09-25 09:11:42天秤座
日期:2013-10-01 16:25:34
9 [报告]
发表于 2012-10-20 23:14 |只看该作者
不要说什么状态机, 我很抵触...
JB状态机,不就是 if / else 嘛

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
10 [报告]
发表于 2012-10-20 23:18 |只看该作者
说的简单, 但能正确的设计估计你是做不到了.

如果我不提状态机, 也许你永远也不懂, 所以你也永远到不了这个level, 稍复杂的逻辑你就应付不了, 所以我希望聪明人安静的好好读读, 不需要说些没用的, 你厉害也不必向我炫耀, 看你有多聪明了.

__BlueGuy__ 发表于 2012-10-20 23:14
不要说什么状态机, 我很抵触...
JB状态机,不就是 if / else 嘛
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP