bayonet开源网络服务器框架正式完成!
博客这几天由于服务器的问题打不开,在这里跟大家抱歉啦
老读者应该都知道,笔者有两个开源项目,分别是:
fuload: 性能测试工具,可以用来给服务器做压力测试
bayonet: 基于两层状态机的epoll服务器框架
对于fuload的介绍,请看这里:
fuload开源压力测试框架完成!
对于bayonet的介绍,请看这里:
有限状态机的C++实现(1)-epoll状态机
有限状态机的C++实现(2)-bayonet开源网络服务器框架
之前由于工作等原因,bayonet一直被搁置,最近有时间,所以就抓紧把bayonet完成了,目前功能上基本已经OK了,我简单列一下功能点:
- 接管了网络,调用方只需要关心业务逻辑
- 配置的方式,快速切换TCP-UDP
- 快速的增加加状态机的状态,业务可以无限拓展
代码编写中,也用到了很多技术,如引用计数来保证野指针不被访问(通过引用计数解决野指针的问题(C&C++)),延迟析构对象,等等。
由于详细介绍是个很庞大的工作,所以这里就直接放出一个基于bayonet写的http代理,我们来看一下,只需要多少代码:
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | #include <iostream> #include <memory> #include <string> #include <vector> #include <set> #include <map> #include "bayonet_frame.h" using namespace std; #define APP_FSM_PROXY 2000 #define APP_FSM_LOGIC2 2001 /** * @brief 获取ContentLen的数字的起始和长度 * * @param strHttpBuf * @param len * * @return */ size_t GetContentLenPos(const string& strHttpBuf, int& len) { string strContentLenKey = "Content-Length"; size_t pos; size_t end_pos; pos = strHttpBuf.find(strContentLenKey); if (pos == string::npos) { return pos; } pos += strContentLenKey.size(); pos = strHttpBuf.find(":", pos); if (pos == string::npos) { return pos; } pos += 1; end_pos = strHttpBuf.find("\r\n", pos); if (end_pos == string::npos) { return end_pos; } pos = strHttpBuf.find_first_not_of(" ", pos);//只能查找字符 if (pos == string::npos || pos >= end_pos) { return pos; } len = end_pos - pos; return pos; } int HttpHandleInput(const char* buf, int len) { string strHttpBuf(buf,len); int contentLenLen = 0; size_t contentLenPos = GetContentLenPos(strHttpBuf,contentLenLen); if (contentLenPos == string::npos) { //说明直接接收完了 return len; } //获取原来的content-len的值 string lenNum = strHttpBuf.substr(contentLenPos, contentLenLen); int iContentLen = atoi(lenNum.c_str()); //接下来我们要看看当前接受的buf大小是否等于 head + content len string spStr = "\r\n\r\n"; size_t spPos = strHttpBuf.find(spStr); if (spPos == string::npos) { //没接收完,继续接收 return 0; } int headLen = spPos+spStr.size(); int realLen = headLen + iContentLen; if (realLen > len) { return 0; } return realLen; } class CMyActor : public CAppActorBase { public: CMyActor() {} virtual ~CMyActor() {} string m_req; string m_rsp; }; class CActionFirst : public IAction { public: int HandleEncodeSendBuf( CSocketActorData* pSocketActor, CAppActorBase* pAppActor, string & strSendBuf, int &len) { CMyActor* app_actor = (CMyActor*)pAppActor; if (app_actor == NULL) { return -1; } strSendBuf = app_actor->m_rsp; len = strSendBuf.size(); return 0; } int HandleInput( CSocketActorData* pSocketActor, CAppActorBase* pAppActor, const char *buf, int len) { return HttpHandleInput(buf,len); } int HandleDecodeRecvBuf( CSocketActorData* pSocketActor, CAppActorBase* pAppActor, const char *buf, int len) { CMyActor * app_actor = new CMyActor(); app_actor->AttachFrame(pSocketActor->GetFrame()); app_actor->AttachCommu(pSocketActor); app_actor->m_req = string(buf,len); //转化状态操作一定要放在最后一步 app_actor->ChangeState(APP_FSM_PROXY); return 0; } }; class CActionGetData: public IAction { public: // 为发送打包 int HandleEncodeSendBuf( CSocketActorData* pSocketActor, CAppActorBase* pAppActor, string & strSendBuf, int &len) { CMyActor* app_actor = (CMyActor*)pAppActor; if (app_actor == NULL) { return -1; } strSendBuf=app_actor->m_req; len = strSendBuf.size(); return 0; } // 回应包完整性检查 int HandleInput( CSocketActorData* pSocketActor, CAppActorBase* pAppActor, const char *buf, int len) { return HttpHandleInput(buf,len); } // 回应包解析 int HandleDecodeRecvBuf( CSocketActorData* pSocketActor, CAppActorBase* pAppActor, const char *buf, int len) { CMyActor* app_actor = (CMyActor*)pAppActor; //因为很有可能,appactor已经由于commu超时的原因被析构掉了 if (app_actor == NULL) { return -1; } app_actor->m_rsp = string(buf,len); return 0; } }; class CAppFsmProxy : public CAppFsmBase { public: virtual ~CAppFsmProxy () {} virtual int HandleEntry(CActionInfoSet *pActionInfoSet, CAppActorBase* pAppActor) { static CActionGetData actionGetData; StActionInfoParam param; param.id = 1; param.ip = "10.6.207.189"; param.port = 80; param.protoType = PROTO_TYPE_TCP; param.pAction = &actionGetData; param.actionType = ACTIONTYPE_SENDRECV; param.timeout_ms = 1000; CActionInfo * pActionInfo = new CActionInfo(); pActionInfo->Init(param); pActionInfoSet->Add(pActionInfo); return 0; } virtual int HandleProcess(CActionInfoSet *pActionInfoSet, CAppActorBase* pAppActor) { set<CActionInfo*> &setAction = pActionInfoSet->GetActionSet(); for(set<CActionInfo*>::iterator it = setAction.begin(); it != setAction.end(); ++it) { trace_log("id:%d,error no:%d,timecost:%u ms",(*it)->GetID(),(*it)->GetErrno(),(*it)->GetTimeCost()); } return APP_FSM_RSP;//代表要回复客户端啦 } virtual int HandleExit(CActionInfoSet *pActionInfoSet, CAppActorBase* pAppActor) { return 0; } }; int main(int argc, const char *argv[]) { CBayonetFrame srv; StFrameParam param; param.infoDir="bayonet"; param.ip="0.0.0.0"; param.port = 20001; param.protoType = PROTO_TYPE_TCP; param.pAction = new CActionFirst(); param.gcMaxCount = 10; int ret = srv.Init(param); if (ret != 0) { return -1; } srv.RegFsm(APP_FSM_PROXY,new CAppFsmProxy()); srv.Process(); return 0; } |
所有发往服务器的http请求,都会被转发到 10.6.207.189 的 80 端口,然后返回给调用端。
这就是所有需要调用端编写的代码了,如果去掉HTTP协议解析的那两个函数,并且去掉空行,一共才不到160行代码。而且你可以随意的再网上加逻辑状态,比如想要在拿到客户端请求之后,再去login服务器验证一下登录态,那只要在注册一个新的状态即可。
如果想看更多的例子,可以去svn上看如下路径的代码:
http://code.google.com/p/bayonet/source/browse/#svn%2Ftrunk%2Fsrc%2Fsvr
http://code.google.com/p/bayonet/source/browse/#svn%2Ftrunk%2Fsrc%2Fsvr2
http://code.google.com/p/bayonet/source/browse/#svn%2Ftrunk%2Fsrc%2Fhttp
当然,现在也还有很多问题,比如性能还没有做优化,各位如果有兴趣的话,欢迎和我一起完成剩下的性能调优工作,我相信这个过程一定比编码阶段有聊的多~
原创文章,版权所有。转载请注明:转载自Vimer的程序世界 [ http://www.vimer.cn ]
本文链接地址: http://www.vimer.cn/?p=2216
学习了
[回复]
说实话,感觉有点高深,呵呵。很厉害的呢,加油~
[回复]
不得啊,你在这方面走的好远啊! 我看你在开源这方面了得,看到你常用nginx, 我现在在看这个源码,看的也是一头晕, 可否给指点一下啊! 谢谢,我可是你的Blog的常客啊!呵呵!
[回复]
Dante 回复:
八月 12th, 2011 at 7:14 下午
过奖哈,只是想多练练手,也沉淀一下。
文中的链接有详细的介绍了bayonet的设计思路,可以看一下哈
其实代码本身不是很复杂,比起ngx的复杂度差得远。。。
[回复]
支持一下 , 感觉不错。
[回复]
楼主,直接编译不过,查了下是#include在高版本的linux里面已经没有了,可以修改为gcc提供的__sync_*系列的built-in函数。 最简单的办法是改为#include 但是要安装libasound2-dev. 支持楼主!
[回复]
bopy 回复:
十二月 30th, 2011 at 12:08 下午
晕….尖括号不能显示 第一处为 asm/atomic.h 第二处是alsa/iatomic.h
[回复]