之前公司的同事写了一个基于epoll的网络服务器,其中涉及到socket状态的转化(如等待接收,接收中,接收完成等),以及socket之间的转化(如验证完ip权限之后,验证完登录态),可见是一个多层次的状态机。
但是在原来的实现中却并没有使用状态模式,导致整个逻辑非常复杂,状态之间的跳转也很难把握。本系列的文章将会通过状态模式来重构整套代码。

状态机模式本身这里就不做详细介绍了,读者可以google一下,笔者在仔细对比过《设计模式之禅》,《研磨设计模式》以及游戏中NPC状态机的实现之后,抽象了如下的一套接口.

IActor是整个状态转化所依附的实体,比如socket可以是recv状态,也可是send状态,那么socket就是一个IActor;而IFsm是状态类的抽象,其中的Init,Process,Fini几个函数都只有一个参数即IActor* obj,IFsm的具体实现类会根据情况来调用obj不同的函数(action)。
所以这里有两个问题:

  • 1.代码中可以看出IFsm是无状态的,即IFsm本身不会存储obj的任何数据,那么为什么不把IFsm的几个函数都定义成static类型呢?这里笔者思索了很久,最终的结论是由于语言的问题,如果是在python中,那么是可以直接传递类名来作为参数的,而在C++中,只能传递一个类的实例在作为参数,这就是原因。
  • 2.既然Process注释中说明返回的是下一个状态,那为什么不直接返回一个IFsm指针,而是返回数字呢,这里会在下面的代码中回答

定义了状态之后,我们还缺少一个状态管理器:

这个是定义在后面会介绍的world里的,之所以有这个管理器

  • 1.是因为C++不同于python,可以直接传递类名
  • 2.通过int来做映射能够更好的实现配置化

所以在状态机启动之前,一般需要有如下代码:

那这里还有一个异议,即有的书上推荐将状态指针定义成CFsmMgr的static变量,当进行状态转化的时候,直接用这个指针即可。但是因为配置化以及底层逻辑与应用逻辑分离的原因,这里个人不建议用如下方式:

接下来是Actor的基类实现:

这个代码也是实现了Actor进行状态转化时的一些逻辑,对于状态转化的触发一般会有两种方式:

  • 1.外界调用ChangeState,即由外界触发-如epoll出发的recv事件导致socket的状态转化成recving
  • 2.状态在执行Process之后自行转化为其他状态-如recving状态在recv结束之后自动转化为recvover状态

所以基于第二点的原因,上面的代码中实现了递归调用的逻辑(这里在状态机的设计中需要注意环的出现)。
下面是一个继承自Actor基类的socketActor,实现如下:

再来看一下状态类的具体实现:

OK,这样一切准备工作就完成,暂时先不把真正的epoll引入进来,我们来模拟一个epoll环境(比较假,没有考虑状态的顺序,大家能明白就行):

main函数如下:

执行结果如下(部分):

这样,一个一层的状态机就实现出来了~后面的文章,我们会面向应用来讨论两层状态机的实现。
附:
代码下载

暂无相关产品

19则回应给“有限状态机的C++实现(1)-epoll状态机”

  1. freeeyes说道:

    很不错的状态机,如果严谨一些的话,可以加一点线程锁。

    [回复]

    Dante 回复:

    因为是用epoll触发的,不存在多线程并发的问题,而且加锁会降低效率。可以考虑在外层封装一层加锁的接口,给不同的场景使用。

    [回复]

  2. 刺猬说道:

    我就比较好奇作者的公司,这样大面积用C++/python,猜测应该是在一些老牌的互联网公司做基础开发/架构相关的吧。
    今天无意中看到原来是腾讯。
    hoho~
    腾讯研发实力确实很强,对博主表示景仰。。。

    [回复]

    Dante 回复:

    哈哈,其实公司也只是c++用的比较多,至于python完全是自学用来解决问题的,呵呵

    [回复]

  3. 时时彩平台说道:

    腾讯研发能力在业内算强么?持怀疑态度

    [回复]

    Dante 回复:

    本博并不代表腾讯立场,所以这个论题也就还是不要讨论了吧~
    大家进行技术交流就好~~

    [回复]

    刺猬 回复:

    恩,就事论事谈下技术,不发散了~~呵呵
    博主胸襟开阔,分寸有度,钦佩下~~

    [回复]

    Dante 回复:

    过奖过奖,哈,常交流~~

    [回复]

    madper 回复:

    业内研发实力最强的, 是当年开发”血狮”的公司, 一款97年的游戏, 我现在用i5来跑, 依旧很卡~ 哈哈哈, 玩笑了~

    [回复]

  4. math games说道:

    post not working in firefox

    [回复]

    Dante 回复:

    ?firefox下不能用吗?

    [回复]

  5. jer说道:

    world::run()方法里面有一个foreach 的用法,个人还没有在C++里面找到这个用法。还请博主帮忙解释一下。谢谢!

    [回复]

    Dante 回复:

    呵呵,这是我之前定义的一个宏哈,当时单独写了一篇文章:
    http://www.vimer.cn/2010/10/%E5%9C%A8c%E4%B8%AD%E5%AE%9E%E7%8E%B0foreach%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%AF%94for_each%E6%9B%B4%E7%AE%80%E6%B4%81%EF%BC%81.html

    [回复]

    jer 回复:

    原来如此,多谢!

    [回复]

  6. Ace说道:

    请问关于 在epoll下 单线程有限状态机模型的服务器设计 有什么书或者文档可以学习吗?

    [回复]

    Dante 回复:

    的确没有什么专门的书讲这个,直接看代码吧,如果不嫌弃的话可以看一下我写的开源项目的代码:
    http://code.google.com/p/bayonet/
    是用两层状态机实现的。

    [回复]

  7. 溪风说道:

    // foreach (m_vecActors, it)
    // {
    // (*it)->ChangeState(state);
    // }

    开始还没看懂这个咋回事,原来博主自己实现的foreach,很好用的状态机

    [回复]

    Dante 回复:

    哈哈,这个宏定义是我觉得自己目前写过的最省代码的宏之一~~

    [回复]

  8. spice630说道:

    函数名doChangeFsm是不是改成 doChangeState比较好?

    [回复]

发表评论