开源网络库【状态机】

http://code.google.com/p/bayonet/downloads/detail?name=bayonet_1.5.4.zip&can=2&q

http://code.google.com/p/bayonet/

 

有限状态机的C++实现-epoll状态机

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*=============================================================================
#  Author:          dantezhu - http://www.vimer.cn
#  Email:           zny2008@gmail.com
#  FileName:        interfaces.h
#  Description:     公共接口
#  Version:         1.0
#  LastChange:      2011-01-19 23:24:33
#  History:         
=============================================================================*/
#ifndef _INTERFACES_H_
#define _INTERFACES_H_
#include <iostream>
#include <map>
using namespace std;
 
class IFsm;
 
class IActor
{
public:
    virtual ~IActor() {}
    virtual int AttachFsmMgr(map<int, IFsm*> *mapFsmMgr)=0;
    virtual int ChangeState(int destState)=0;
};
 
 
class IFsm
{
public:
    virtual ~IFsm() {}
    /**
     * @brief   在进入这个状态的时候,obj需要做的事情
     *
     * @param   obj
     *
     * @return  0           succ
     *          else        fail
     */
    virtual int Init(IActor* obj)=0;
 
    /**
     * @brief   执行这个状态该做的事情
     *
     * @param   obj
     *
     * @return  应该进入的另一个状态
     *          0           结束本次Process执行,不进入其他状态
     *          <0          结束整个请求(obj需要被后续删除)
     *          else        其他状态(可以返回自己,但是会造成循环,有点危险)
     *          
     */
    virtual int Process(IActor* obj)=0;
 
    /**
     * @brief   退出这个状态时需要做的事情
     *
     * @param   obj
     *
     * @return  0           succ
     *          else        fail
     */
    virtual int Fini(IActor* obj)=0;
};
 
#endif

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指针,而是返回数字呢,这里会在下面的代码中回答

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

1
map<int, IFsm* > m_mapFsmMgr;

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

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

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

1
2
3
m_mapFsmMgr[1]=new CWaitSendFsm();
m_mapFsmMgr[2]=new CSendingFsm();
m_mapFsmMgr[3]=new CSendOverFsm();

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CFsmMgr
{
public:
    CFsmMgr () {}
    virtual ~CFsmMgr () {}
 
public:
    static IFsm* WaitSendFsmObj;
    static IFsm* SendingFsmObj;
    static IFsm* SendOverFsmObj;
};
IFsm* CFsmMgr::WaitSendFsmObj = new CWaitSendFsm();
IFsm* CFsmMgr::SendingFsmObj = new CSendingFsm();
IFsm* CFsmMgr::SendOverFsmObj = new CSendOverFsm();

接下来是Actor的基类实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class CBaseActor : public IActor
{
public:
    CBaseActor () {
        m_Fsm = NULL;
        m_mapFsmMgr = NULL;
    }
    virtual ~CBaseActor () {}
 
    int AttachFsmMgr(map<int, IFsm*> * mapFsmMgr)
    {
        m_mapFsmMgr = mapFsmMgr;
        return 0;
    }
 
    int ChangeState(int destState)
    {
        if (m_mapFsmMgr == NULL)
        {
            return -1;
        }
 
        if (0 == destState)
        {
            //此次处理结束
            return 0;
        }
        else if (destState < 0)
        {
            //需要关闭整个请求
            return destState;
        }
        IFsm * destFsm = NULL;
        destFsm = (*m_mapFsmMgr)[destState];
        int state = doChangeFsm(destFsm);
        return ChangeState(state);
    }
private:
    int doChangeFsm(IFsm* destFsm)
    {
        if (destFsm == NULL)
        {
            return 0;
        }
 
        if (m_Fsm != destFsm)
        {
            if (m_Fsm != NULL)
            {
                m_Fsm->Fini(this);
            }
            m_Fsm = destFsm;
            m_Fsm->Init(this);
        }
        return m_Fsm->Process(this);
    }
 
 
protected:
    IFsm* m_Fsm;
    map<int, IFsm*> *m_mapFsmMgr;
};

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class CSocketActor : public CBaseActor
{
public:
    CSocketActor () {}
    virtual ~CSocketActor () {}
 
    int HandleSend()
    {
        cout<<"sending"<<endl;
        if (rand() % 5 == 0)
        {
            //代表发送完了
            return 1;
        }
        return 0;
    }
    int HandleRecv()
    {
        cout<<"recving"<<endl;
        return 0;
    }
    int HandleError()
    {
        cout<<"error"<<endl;
        return 0;
    }
    int HandleTimeout()
    {
        cout<<"timeout"<<endl;
        return 0;
    }
};

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//waitsend sending sendover waitrecv recving recvover waitclose closing closeover error timeout
 
class CWaitSendFsm : public IFsm
{
public:
    CWaitSendFsm () {}
    virtual ~CWaitSendFsm () {}
    virtual int Init(IActor* obj)
    {
        cout<<"Init WaitSend"<<endl;
        return 0;
    }
    virtual int Process(IActor* obj)
    {
        cout<<"Process WaitSend"<<endl;
        return 0;
    }
    virtual int Fini(IActor* obj)
    {
        cout<<"Fini WaitSend"<<endl;
        return 0;
    }
};
class CSendingFsm : public IFsm
{
public:
    CSendingFsm () {}
    virtual ~CSendingFsm () {}
    virtual int Init(IActor* obj)
    {
        cout<<"Init Sending"<<endl;
        return 0;
    }
    virtual int Process(IActor* obj)
    {
        cout<<"Process Sending"<<endl;
        CSocketActor * chirdObj = (CSocketActor*) obj;
        int ret = chirdObj->HandleSend();
        if (ret == 1)
        {
            return 3;
        }
        return 0;
    }
    virtual int Fini(IActor* obj)
    {
        cout<<"Fini Sending"<<endl;
        return 0;
    }
};
class CSendOverFsm : public IFsm
{
public:
    CSendOverFsm () {}
    virtual ~CSendOverFsm () {}
    virtual int Init(IActor* obj)
    {
        cout<<"Init SendOver"<<endl;
        return 0;
    }
    virtual int Process(IActor* obj)
    {
        cout<<"Process SendOver"<<endl;
        return 0;
    }
    virtual int Fini(IActor* obj)
    {
        cout<<"Fini SendOver"<<endl;
        return 0;
    }
};

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class CWorld
{
public:
    CWorld () {
        srand(time(NULL));
    }
    virtual ~CWorld () {}
 
    int Init()
    {
        int count = 10;
 
        m_mapFsmMgr[1]=new CWaitSendFsm();
        m_mapFsmMgr[2]=new CSendingFsm();
        m_mapFsmMgr[3]=new CSendOverFsm();
 
        for (int i = 0; i < count; i++)
        {
            IActor * actor = new CSocketActor();
            m_vecActors.push_back(actor);
 
            actor->AttachFsmMgr(&m_mapFsmMgr);
            actor->ChangeState(1);
        }
        return 0;
    }
    int Run()
    {
        while (true)
        {
            int state = 0;
            int val = rand() % 5;
            switch(val)
            {
                case 0:
                case 1:
                    state = 1;
                    break;
                case 2:
                    state = 2;
                    break;
                default:
                    state = 3;
                    break;
            }
            foreach (m_vecActors, it)
            {
                (*it)->ChangeState(state);   
            }
            sleep(1);
        }
        return 0;
    }
 
private:
    vector<IActor*> m_vecActors;
    map<int, IFsm* > m_mapFsmMgr;
};

main函数如下:

1
2
3
4
5
6
7
8
#include "fsm_achieve.h"
int main(int argc, const char *argv[])
{
    CWorld world;
    world.Init();
    world.Run();
    return 0;
}

执行结果如下(部分):

Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Init WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend
Process WaitSend

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

 

 

有限状态机的C++实现-bayonet开源网络服务器框架

 

上一篇文章: 有限状态机的C++实现(1)-epoll状态机,我们今天来介绍更复杂和深入的部分。

为什么会在标题中提到bayonet这个开源项目呢?笔者本人一直想要写一套架构优美、功能完善的异步server框架,也看过很多朋友、同事实现的版本,虽然功能上基本能满足需求,但是架构上我却始终觉得是有瑕疵的,直到后来和同事讨论,发现可以让一个客户端请求的到来作为一个session,而之后的每一次与其他server的交互都可以看作是一次状态转化,才感觉架构比较合理了。

简单来说即,一个session从开始到介绍会经历两种状态机的变化:

  • 1.业务逻辑层面的状态变化,例如先验证登录态,再验证权限,再获取用户资料
  • 2.每一个与其他server交互的socket自身的状态变化,如recv、send、等,而socket的状态变化会触发逻辑层的状态变化。

按照这种思路,目前的代码开发已经完成了70%,即可以正常的进行一个session的开始和结束,主要还缺一些细节的代码,比如超时的检测及超时之后的处理,健全的统计之类。好了,我们来用vs看一下代码的整体类图(图压缩比较严重,请单击后查看):

1

每个类的用处已经在途中简单说明了,这里就不再赘述,我们重点来看一下用这个框架来实现一个逻辑server时需要做哪些事情。
svr2目录下的main.cpp即实现了一个最简单的server,我们按部分来看其实现:
1.逻辑层状态的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class CAppFsmLogic1 : public CAppFsmBase
{
public:
    virtual ~CAppFsmLogic1 () {}
    virtual int HandleEntry(CActionInfoSet *pActionInfoSet, CAppActorBase* pAppActor)
    {
        static CActionFirst actionFirst;
        StActionInfoParam param;
        param.id = 1;
        param.ip = "127.0.0.1";
        param.port = 100;
        param.protoType = PROTO_TYPE_UDP;
        param.pAction = &actionFirst;
        param.actionType = ACTIONTYPE_SENDONLY;
        param.timeout_ms = 1000;
 
        CActionInfo * pActionInfo = new CActionInfo();
        pActionInfo->Init(param);
        pActionInfo->SetAppActor(pAppActor);
        pActionInfoSet->Add(pActionInfo);
        return 0;
    }
    virtual int HandleProcess(CActionInfoSet *pActionInfoSet, CAppActorBase* pAppActor)
    {
        trace_log("HandleProcess");
        set<CActionInfo*> &setAction = pActionInfoSet->GetActionSet();
        for(set<CActionInfo*>::iterator it = setAction.begin(); it != setAction.end(); ++it)
        {
            trace_log("error no:%d",(*it)->GetErrno());
        }
        return APP_FSM_RSP;//代表要回复客户端啦
    }
    virtual int HandleExit(CActionInfoSet *pActionInfoSet, CAppActorBase* pAppActor)
    {
        return 0;
    }
};

CAppFsmLogic1是一个逻辑层的状态:

  • 1.HandleEntry代表当这个状态第一次进入的时候要做的事情,其函数中创建了一个向其他server发包的action(CActionFirst的定义我们在后面介绍)。
  • 2.HandleProcess代表当这个状态的所有action都完成时需要做的事情,return APP_FSM_RSP;代表向客户端回包

看到这里大家应该很奇怪,对于每一个socket,判断收包长度以及受到包之后的解包在哪里完成呢?所以我们还需要定义action:
2.action的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#define APP_FSM_LOGIC1 2000
 
class CAppFsmLogic1;
class CActionFirst : public IAction
{
public:
    // 为发送打包
    int HandleEncodeSendBuf(
            IActor* pSocketActor,
            IActor* pAppActor,
            string & strSendBuf,
            int &len)
    {
        trace_log("send");
        strSendBuf="woainizhende111111";
        len = strSendBuf.size();
        return 0;
    }
 
    // 回应包完整性检查
    int HandleInput(
            IActor* pSocketActor,
            IActor* pAppActor,
            const char *buf,
            int len)
    {
        return len;
    }
 
    // 回应包解析
    int HandleDecodeRecvBuf(
            IActor* pSocketActor,
            IActor* pAppActor,
            const char *buf, 
            int len)
    {
        CAppActorBase * app_actor = new CAppActorBase();
        app_actor->AttachFrame(pSocketActor->GetFrame());
        app_actor->AttachCommu(pSocketActor);
        app_actor->ChangeState(APP_FSM_LOGIC1);
        trace_log("listen tcp HandleDecodeRecvBuf");
        return 0;
    }
};

每个函数的意义已经在代码中说明了,可以看出在HandleDecodeRecvBuf中创建逻辑层的actor: app_actor,并ChangeState为APP_FSM_LOGIC1。
最后就是main函数的实现了:
3.main函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, const char *argv[])
{
    CBayonetFrame srv;
    StFrameParam param;
    param.ip="0.0.0.0";
    param.port = 10001;
    param.bKeepcnt= true;
    //param.protoType = PROTO_TYPE_UDP;
    param.protoType = PROTO_TYPE_TCP;
    param.pAction = new CActionFirst();
 
    srv.Init(param);
    srv.RegFsm(APP_FSM_LOGIC1,new CAppFsmLogic1());
    srv.Process();
    return 0;
}

注释的部分是可以随时切换TCP还是UDP的。
当然作为一个server来说,这里还是太过简单了,比如信号的处理等都没有加上,但是笔者认为那是业务代码需要做的逻辑,所以并没有放到框架中。

OK,整个项目的结构就是这个样子了。
但是也不得不说点扫兴的话,由于笔者最近有另外一个项目需要投入大量的精力,所以该项目的更新可能会被延缓,这是我所不愿意看到的,所以很希望有志同道合的朋友能够加入到这个项目的开发中来,一起把这个事情做出来。

按照我当初的想法,压力测试框架fuload已经就绪了,等到bayonet完成,我们就用fuload来测试一下bayonet的性能究竟如何。

最后,附上bayonet的项目地址:
http://code.google.com/p/bayonet/

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值