接着上一篇文章: 有限状态机的C++实现(1)-epoll状态机,我们今天来介绍更复杂和深入的部分。
为什么会在标题中提到bayonet这个开源项目呢?笔者本人一直想要写一套架构优美、功能完善的异步server框架,也看过很多朋友、同事实现的版本,虽然功能上基本能满足需求,但是架构上我却始终觉得是有瑕疵的,直到后来和同事讨论,发现可以让一个客户端请求的到来作为一个session,而之后的每一次与其他server的交互都可以看作是一次状态转化,才感觉架构比较合理了。
简单来说即,一个session从开始到介绍会经历两种状态机的变化:
- 1.业务逻辑层面的状态变化,例如先验证登录态,再验证权限,再获取用户资料
- 2.每一个与其他server交互的socket自身的状态变化,如recv、send、等,而socket的状态变化会触发逻辑层的状态变化。
按照这种思路,目前的代码开发已经完成了70%,即可以正常的进行一个session的开始和结束,主要还缺一些细节的代码,比如超时的检测及超时之后的处理,健全的统计之类。好了,我们来用vs看一下代码的整体类图(图压缩比较严重,请单击后查看):
每个类的用处已经在途中简单说明了,这里就不再赘述,我们重点来看一下用这个框架来实现一个逻辑server时需要做哪些事情。
svr2目录下的main.cpp即实现了一个最简单的server,我们按部分来看其实现:
1.逻辑层状态的定义
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的定义
#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函数实现
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/
十分欢迎大家感兴趣的朋友与我联系。