1. 闲序

  游戏服务器之间通信大多采用异步消息通信。而消息打包常用格式有:google protobuff,facebook thrift, 千千万万种自定义二进制格式,和JSON。前三种都是二进制格式,针对C++开发者都是非常方便的,效率和包大小(数据冗余度)也比较理想。而JSON是字符串协议,encode和decode需要不小的开销。500字节json字符串解析大约需要1ms左右。JSON在脚本语言中非常常见,比如WEB应用、Social Game等,原因是web应用通过多进程分摊了JSON解析的CPU开销,而且这些应用实时性不强。JSON相对于二进制协议有点就是它是自描述的,调试JSON消息非常的方便,如果消息出错简单的将消息log到文件,肉眼即可分辨真伪(眼力不行,有工具相帮http://www.jsoneditoronline.org/,更多工具参见http://json.org/)。事实上json由于是字符串,压缩传输也可以达到比较理想的压缩比。

  我们的Social Game 客户端都是Flash,Flash 工程师们非常喜欢使用Json,几款游戏Flash和Php通信都是使用Json。新的游戏支持实时对战,后台使用c++实现,我们仍然采用JSON。在后台计算时为了保证实时性,我们一般把json解析放到网络线程(多线程),解析成c++的struct 特定类型再post到逻辑线程(单线程)处理。这样Json的解析可以分摊到多个CPU上,并且不浪费主逻辑线程cpu。

  目前遇到的问题是,如果每增加一个接口,就增加一个struct,再在网络处理逻辑函数中增加json解析代码(包括错误处理),再跟flash联调协议。还有一个挺烦人的时接口文档每次都要更新,如果直接把定义struct的头文件给flash,但是貌似不太优雅,还是有份文档比较正式。

  我参考了一下google protobuf 和 facebook thrift,想设计如下消息定义方式。

2. 定义idl文件

  interface description language ?其实我只有消息格式描述,并无接口,但是idl比较容易接受。

  假如说需要一个消息描述student的数据,那么使用 我定义idl描述其内容如下,student.idl

//! 定义student消息格式,版本号1stuct student_t(version=1){//! 描述student需要子类型book   struct book_t(version=1)    {//! book中包含两个字段,ages 16位数字,content字符串,可为空,默认值为”oh nice“
        int16  pages;
      string content(default="Oh Nice!");   }//! 定义年龄,分数,姓名,都是基本类型//! 定义friends为数组,单项类型为字符串,对应json数组//! 定义books为字典,key为字符串,项为book结构,对应json对象结构  int8  age;  float grade(default=0);  string name;  array<string>  friends;  dictionary<string, book_t> books;};

3. 使用idl 代码生成器生成消息定义c++ 头文件

  idl_generator.py student.idl -l cpp -o msg_def.h

生成msg_def.h

idl_generator.py@ http://ffown.googlecode.com/svn/trunk/fflib/lib/generator/

4. 使用生成的C++ 消息头文件

  生成的头文件内容是:

struct student_t{struct book_t    {        int16_t     pages;string      contents;    };    int8_t              age;float               grade;string              name;    vector<string>      friends;    map<string, book_t> books;};//! 模板类,T为回调对象类型,每种msg 类型T中都需要定义相应的handle函数, R代表请求的socket类型指针,这里使用泛型表示template<typename T, typename R>class msg_dispather_t{    typedef runtime_error        msg_exception_t;//!请求格式出错,抛出异常    typedef rapidjson::Document  json_dom_t;   //! 使用rapidjson库实现json解析,但是某个时刻可能替换该库,故typedef    typedef rapidjson::Value     json_value_t;   //! rapidjson源代码:http://code.google.com/p/rapidjson/    typedef R                    socket_ptr_t;  //! 请求socket    typedef int (msg_dispather_t<T, R>::*reg_func_t)(const json_value_t&, socket_ptr_t); //! 消息对应的解析函数public:    msg_dispather_t(T& msg_handler_):        m_msg_handler(msg_handler_)    {        m_reg_func["student_t"] = &msg_dispather_t<T, R>::student_t_dispacher;//! 所有的消息都在构造时注册解析函数,解析函数是通过idl自动生成的    }int dispath(const string& json_, socket_ptr_t sock_);//! 接口函数,使用者只需单点接入dispatch,消息会自动派发到msg_handler特定的handle函数

private:int student_t_dispacher(const json_value_t& jval_, socket_ptr_t sock_)//! 每个消息都会自动生成特定的消息解析函数,前缀为消息名称    {        student_t s_val;const json_value_t& age      = jval_["age"];const json_value_t& grade    = jval_["grade"];const json_value_t& name     = jval_["name"];const json_value_t& friends  = jval_["friends"];const json_value_t& books    = jval_["books"];char buff[512];

if (false == age.IsNumber())        {            snprintf(buff, sizeof(buff), "student::age[int] field needed");throw msg_exception_t(buff);        }        s_val.age = age.GetInt();if (false == grade.IsDouble())        {            snprintf(buff, sizeof(buff), "student::grade[float] field needed");throw msg_exception_t(buff);        }        s_val.grade = grade.GetDouble();if (false == name.IsString())        {            snprintf(buff, sizeof(buff), "student::name[string] field needed");throw msg_exception_t(buff);        }        s_val.name = name.GetString();if (false == friends.IsArray())        {            snprintf(buff, sizeof(buff), "student::friends[Array] field needed");throw msg_exception_t(buff);        }for (rapidjson::SizeType i = 0; i < friends.Size(); i++)        {const json_value_t& val = friends[i];if (false == val.IsString())            {                snprintf(buff, sizeof(buff), "student::friends field at[%u] must string", i);throw msg_exception_t(buff);            }            s_val.friends.push_back(val.GetString());        }if (false == books.IsObject())        {            snprintf(buff, sizeof(buff), "student::books[Object] field needed");throw msg_exception_t(buff);        }        rapidjson::Document::ConstMemberIterator it = books.MemberBegin();for (; it != books.MemberEnd(); ++it)        {            student_t::book_t book_val;const  json_value_t& name = it->name;if (false == name.IsString())            {                snprintf(buff, sizeof(buff), "student::books[Object] key must [string]");throw msg_exception_t(buff);            }

const  json_value_t& val = it->value;if (false == val.IsObject())            {                snprintf(buff, sizeof(buff), "student::books[Object] value must [Object]");throw msg_exception_t(buff);            }

const  json_value_t& book_pages   = val["pages"];const  json_value_t& book_contens = val["contents"];if (false == book_pages.IsNumber())            {                snprintf(buff, sizeof(buff), "student::books::pages[Number] field needed");throw msg_exception_t(buff);            }            book_val.pages    = book_pages.GetInt();if (false == book_contens.IsString())            {                snprintf(buff, sizeof(buff), "student::books::book_contens[String] field needed");throw msg_exception_t(buff);            }            book_val.contents = book_contens.GetString();            s_val.books[name.GetString()] = book_val;        }

        m_msg_handler.handle(s_val, sock_);//! 由于msg_handler中重载了针对所有消息的handle函数,此函数会被正确的派发到逻辑层return 0;    }

private:    T&                      m_msg_handler;    map<string, reg_func_t> m_reg_func;};

template<typename T, typename R>int msg_dispather_t<T, R>::dispath(const string& json_, socket_ptr_t sock_){    json_dom_t document;  // Default template parameter uses UTF8 and MemoryPoolAllocator.    if (document.Parse<0>(json_.c_str()).HasParseError())    {throw msg_exception_t("json format not right");    }if (false == document.IsObject() && false == document.Empty())    {throw msg_exception_t("json must has one field");    }

const json_value_t& val = document.MemberBegin()->name;const char* func_name   = val.GetString();    typename map<string, reg_func_t>::const_iterator it = m_reg_func.find(func_name);

if (it == m_reg_func.end())//! 查找解析派发函数是否存在    {char buff[512];        snprintf(buff, sizeof(buff), "msg not supported<%s>", func_name);throw msg_exception_t(buff);return -1;    }    reg_func_t func = it->second;

    (this->*func)(document.MemberBegin()->value, sock_);return 0;}

5. 逻辑层处理消息

  逻辑层不需要编写繁杂的json解析和错误处理,只要没有触发异常,消息会自动派发到msg_handler中的handle函数,所以逻辑层只需针对每一个消息类型

都重载一个handle函数即可,示例处理代码如下:

class msg_handler_t{public:    typedef int socket_ptr_t;public:void handle(const student_t& s_,  socket_ptr_t sock_)    {        cout  << "msg_handler_t::handle:\n";        cout  << "age:" << int(s_.age) << " grade:" << s_.grade << " friends:"<< s_.friends.size() << " name:"              << s_.name << " books:" << s_.books.size() <<"\n";    }};

int main(int argc, char* argv[]){try    {string tmp = "{\"student_t\":{\"age\":123,\"grade\":1.2,\"name\":\"bible\",\"friends\":[\"a\",\"b\"],""\"books\":{\"bible\":{\"pages\":123,\"contents\":\"oh nice\"}}}}";        msg_handler_t xxx;        msg_dispather_t<msg_handler_t, msg_handler_t::socket_ptr_t> p(xxx);        p.dispath(tmp, 0);    }catch(exception& e)    {        cout <<"e:"<< e.what() <<"\n";    }}

示例代码: http://ffown.googlecode.com/svn/trunk/fflib/lib/generator/

6. More

  1> json解析目前使用 rapidjson,号称效率极佳,此处用它最大的好处是只需包含头文件即可使用

  2> 分析解析idl 文件程序使用python编写(正在编写中)

  3> idl 定义中支持namespace 为佳,但考虑复杂性,第一版本暂不支持。

   4> 本篇只实现了json to struct,实际上 struct to struct 也很容易实现,json 字符串的第一个字符为'{',而如果采用二进制消息,第一个字符表示消息类型的字符串长度(一个字节足以),如"sdudent_t",那么首字节应该为9,并且设定首字节首位为1,那么描述类型的字符串长度最大为128个字符(足以了)。放到下篇再搞,睡了。

C++中消息自动派发之一 About JSON相关推荐

  1. RocketMQ3.2.2生产者发送消息自动创建Topic队列数无法超过4个

    问题现象 RocketMQ3.2.2版本,测试时尝试发送消息时自动创建Topic,设置了队列数量为8: producer.setDefaultTopicQueueNums(8); 同时设置broker ...

  2. android触摸消息的派发过程

    1.触摸消息是消息获取模块直接派发给应用程序的. 2.触摸消息在处理时, 需要根据触摸坐标计算该消息应该派发给哪个View/ViewGroup, 在案件取消处理中不存在 该计算过程. 3.没有类似&q ...

  3. web直播+聊天室功能(阿里云播放器和即时通信IM聊天、消息自动滚动)

    左边为视频直播,右边为聊天 解决问题: 1.直播的时候,播放器可以点击,并且可以暂停,解决办法是在播放器上加一次遮罩 2.tim聊天功能要注册阿里云的账号,下载自己的sdk和js插件 3.直播功能下载 ...

  4. 微信开发中消息回复的代码_消息中的消息

    微信开发中消息回复的代码 Ste·ga·no·graph·y / stegəˈnägrəfi / (noun): the practice of concealing messages or info ...

  5. php中定时刷新当前页面,php中怎么自动刷新页面

    php中自动刷新页面的方法:1.使用header()函数来实现自动刷新页面:2.在PHP脚本中嵌入JS代码,通过JS代码来实现自动刷新页面. php中怎么自动刷新页面? 1.使用header()函数来 ...

  6. Golang中的自动伸缩和自防御设计

    Raygun服务由许多活动组件构成,每个组件用于特定的任务.其中一个模块是用Golang编写的,负责对iOS崩溃报告进行处理.简而言之,它接受本机iOS崩溃报告,查找相关的dSYM文件,并生成开发者可 ...

  7. spring jms 消息_Spring JMS,消息自动转换,JMS模板

    spring jms 消息 在我的一个项目中,我应该创建一个消息路由器,就像所有路由器一样,它应该从一个主题获取JMS消息并将其放入另一个主题. 该消息本身是JMS文本消息,实际上包含XML消息. 收 ...

  8. Spring JMS,消息自动转换,JMS模板

    在我的一个项目中,我应该创建一个消息路由器,就像所有路由器都应该从一个主题中提取JMS消息并将其放入另一个主题中一样. 该消息本身是JMS文本消息,实际上包含XML消息. 收到消息后,我还应该添加一些 ...

  9. always on_Always On可用性组中的自动播种

    always on 介绍 (Introduction) In SQL Server Always On Availability Groups, we can add a database into ...

最新文章

  1. JDK 17 要来了!会带来什么新特性?不好意思,我还在 JDK 7 踏步走...
  2. mysql所有表查询
  3. MyBatis-动态SQL
  4. windows php postgre,windows下php不支持pgsql的解决方法
  5. CSVN部署安装,实现web管理svn
  6. pom添加依赖后不报错但是代码依然缺少依赖
  7. 【图像超分辨率论文】BasicVSR++: Improving Video Super-Resolution with Enhanced Propagation and Alignment
  8. 数据库系统原理(第四章:SQL与关系数据库基本操作 )
  9. android ble蓝牙接收不到数据_Android蓝牙4.0 Ble读写数据详解 -2
  10. 【 IT版 】啥是佩奇?
  11. Python3 安装 mysql-python 与ImportError: No module named 'ConfigParser'报错解决方法
  12. 计算机vfp程序设计题,2012年计算机级VFP程序设计基础习题及答案.doc
  13. orale如果是整数后面补小数点0_Win10完美整数大小磁盘分区
  14. 查看oracle数据库版本
  15. mcafee杀死oracle,如何从卸载McAfee卸载工具
  16. Android面试必问之Handler机制
  17. 最新美团Java面试题目(共3面)
  18. 计算机键盘功能键介绍6,笔记本全部按键功能的详细说明笔记本电脑键盘上有什么区别...
  19. python期货基本面分析_Python量化炒期货入门与实战技巧
  20. 五子棋 手打稍加改变自慕课网hyman

热门文章

  1. throw和throws的区别是什么简答_Throws的作用是 ( )_学小易找答案...
  2. oracle中定义表类型数据,oracle 定义表字段域的数据类型
  3. 在linux看数据库表空间,Linux平台达梦数据库V7之表空间管理
  4. python generator
  5. C语言 FileStreaming buffer
  6. Sphinx 文档例子
  7. Linux bash 命令
  8. Linux mv命令
  9. JAVA就诊叫号_基于B/S的JAVA门诊就诊系统
  10. ERP核心业务流程和Oracle-ERP业务和数据对象分析