C++中消息自动派发之四 使用IDL构建Chat Server

  前一篇blog 讲了如何实现IDL 解析器,本篇通过IDL解析器构建一个聊天服务器程序。本程序用来测试IDL解析器的功能,网络层使用前边blog中介绍的ffown库。我们只需定义chat.idl文件,idl解析器自动生成消息排放代码,省了每次再去繁琐的编写消息解析、判断代码。

  IDL解析器介绍:http://www.cnblogs.com/zhiranok/archive/2012/02/23/json_to_cpp_struct_idl_parser_second.html

  ffown socket库:http://www.cnblogs.com/zhiranok/archive/2011/12/24/cpp_epoll_socket.html

1. 场景设定

  1>. user 登入系统,检查是否重登陆,若登陆过则返回出错(由于无passwor认证,只好采用”抢注“方式,uid抢先登入者可登入)。user登入后须获取在线的用户ID列表。同时该user上线消息也应该推送给在线的其他用户。

  2>. user 登出,从服务器中删除用户信息,关闭socket。广播给所有在线用户该用户下线。

  3>. chat 聊天。用户可以给在线的某个用户发送聊天信息,也可以多人聊天,甚至可以给所有人广播。

2. 服务器模块设计

  1>. 网络层

    开发网络程序必须有一个稳定、高效的网络库框架。目前流行的基于C++的网络程序库有:

    a. Boost ASIO

    b. Libevent

    c. unix socket API

    这里极力推荐ASIO,两年来开发的多个服务器程序都是基于ASIO实现的,自己也非常的熟悉。自己也阅读过ASIO的源码,收获了一些非常宝贵的异步IO的设计技巧。网上有些人评论ASIO太大,太臃肿,我觉得其实不然。虽然ASIO为实现跨平台而增加了很多封装、宏,但是ASIO对应SOCKET的封装还是比较简单的。ASIO中最巧妙的就是所有IO模型都是建立在io_service上,这样网络层非常容易使用多线程。针对ASIO的分析详见前边的blog:http://www.cnblogs.com/zhiranok/archive/2011/09/04/boost_asio_io_service_CPP.html。使用ASIO还有一个好处是,你可以充分享受Boost库(如Lamda、shared_ptr、thread)带来的便捷,生产力立刻提升一个台阶。个人觉得使用ASIO需要有一定的模式基础。我也是用ASIO封装过一个网络层参见:

http://www.cnblogs.com/zhiranok/archive/2011/12/18/ffasio.html

    当然喜欢搞底层的工程师都爱自己构建一个socket通讯库,这也无可厚非(即使有点重复造轮子),毕竟这样个人或者团队可以完全控制代码库的质量,出了问题也容易排查,而且也不需要太大的工作量。使用ASIO时我们就出现过问题,1.39版本的asio异步连接有bug,有非常小的概率回调函数不能被调用(大并发测试),更新到1-44就ok了。个人认为,对于一个团队,一个成熟的网络框架是成功的基石。

    本示例中网络层传输协议非常简单,消息体body的长度(字符串形式)+\r\n + 消息体body,这样可以直接使用telnet测试本程序。

  2>. 消息派发层

    我曾使用过google protocol和facebook thrift,protocol只是封装了消息封装,不具有消息派发功能,thrift实际上是一个rpc框架,自动能够生成client代码或non blocking server框架代码。但是我们开发实时在线游戏后台程序都是基于消息的,所以开发一个类似protoco这样的东东还是很有意义的。用法是编写消息的idl文件,定义请求消息格式和响应消息格式。idl文件实际上也扮演了和client的接口描述文档角色。接下来使用idl 解析器分析idl 自动生成消息派发代码。

    如在chat server示例中,我定义了chat.idl, 生成消息派发框架代码的方式是:

    idl_generator.py idl/chat.idl include/msg_def.h

    生成的代码文件为msg_def.h

    其中idl文件定义为:

struct login_req_t
{
    uint32 uid;
};

struct chat_to_some_req_t
{
    array<uint32> dest_uids;
    string               content;
};

struct user_login_ret_t
{
    uint32 uid;
};

struct user_logout_ret_t
{
    uint32 uid;
};

struct online_list_ret_t
{
    array<uint32> uids;
};

struct chat_content_ret_t
{
    uint32 from_uid;
    string   content;
};

  3> 领域逻辑层

    领域逻辑尽量保证跟需求分析中建立的模型一致,DDD驱动。所以尽量不要集成太多网络层或消息解析层的代码。我的思路是将消息解析用idl解析器实现,网络层使用成熟的框架,这样我们只需集中精力测试逻辑层的正确即可。

    本chat server只是要测试一下idl 解析器的功能,所以没有集成太多功能。

     主要代码片段为:

int chat_service_t::handle_broken(socket_ptr_t sock_)
{
    uid_t* user = sock_->get_data<uid_t>();
    if (NULL == user)
    {
        delete sock_;
        return 0;
    }

    lock_guard_t lock(m_mutex);
    m_clients.erase(*user);

    user_logout_ret_t ret_msg;
    ret_msg.uid = *user;
    string json_msg = ret_msg.encode_json();
    delete sock_;

    map<uid_t, socket_ptr_t>::iterator it = m_clients.begin();
    for (; it != m_clients.end(); ++it)
    {
        it->second->async_send(json_msg);
    }
    return 0;
}


int chat_service_t::handle_msg(const message_t& msg_, socket_ptr_t sock_)
{
    try
    {
        m_msg_dispather.dispath(msg_.get_body() , sock_);
    }
    catch(exception& e)
    {
        sock_->async_send("msg not supported!");
        logtrace((CHAT_SERVICE, "chat_service_t::handle_msg exception<%s>", e.what()));
        sock_->close();
    }
    return 0;
}

int chat_service_t::handle(shared_ptr_t<login_req_t> req_, socket_ptr_t sock_)
{
    logtrace((CHAT_SERVICE, "chat_service_t::handle login_req_t uid<%u>", req_->uid));
    lock_guard_t lock(m_mutex);

    pair<map<uid_t, socket_ptr_t>::iterator, bool> ret =  m_clients.insert(make_pair(req_->uid, sock_));
    if (false == ret.second)
    {
        sock_->close();
        return -1;
    }

    uid_t* user = new uid_t(req_->uid);
    sock_->set_data(user);

    user_login_ret_t login_ret;
    login_ret.uid = req_->uid;
    string login_json = login_ret.encode_json();

    online_list_ret_t online_list;

    map<uid_t, socket_ptr_t>::iterator it = m_clients.begin();
    for (; it != m_clients.end(); ++it)
    {
        online_list.uids.push_back(it->first);
        it->second->async_send(login_json);
    }

    sock_->async_send(online_list.encode_json());
    return 0;
}

int chat_service_t::handle(shared_ptr_t<chat_to_some_req_t> req_, socket_ptr_t sock_)
{
    lock_guard_t lock(m_mutex);

    chat_content_ret_t content_ret;
    content_ret.from_uid = *sock_->get_data<uid_t>();
    content_ret.content  = req_->content;

    string json_msg =  content_ret.encode_json();
    for (size_t i = 0; i < req_->dest_uids.size(); ++i)
    {
        m_clients[req_->dest_uids[i]]->async_send(json_msg);
    }
    return 0;
}

 完整代码参见:

https://ffown.googlecode.com/svn/trunk/example/chat_server

 3. 总结

   1. 网络层使用ffown,目前还没有socket管理模块主要是心跳功能,后续加入。

   2. 日志直接使用printf完成,应该使用一个日志模块完成日志的格式化、输出等。

   3. idl 消息派发框架支持者json字符串协议,二进制协议可以后续加入,而网络层应该具有压缩传输功能

   4. 由于只是示例程序,client端我简单用python实现了一个。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ThoughtWorks

前端不止:Web性能优化 - 关键渲染路径以及优化策略

我问你:“当你从搜索引擎的结果页面选择打开一条搜索结果时,你觉得多长时间之后,如果页面还处于白屏或者没有加载到关键信息,你会选择关掉这个窗口?”

872
来自专栏Tech Talk

从CPU爆表问题排查谈Java性能监测之道

记一次Java线上服务器CPU过载问题的排查过程,详解排查过程中用到的Java性能监测工具:jvisualvm、jstack、jstat、jmap。

46810
来自专栏Java进阶

聊聊TCP传输的滑动窗口协议的演进

35210
来自专栏WeTest质量开放平台团队的专栏

iOS Push详述,了解一下?

本文主要对iOS Push的在线push、本地push及离线(远程)push进行梳理,介绍了相关逻辑,测试时要注意的要点以及相关工具。

5536
来自专栏码神联盟

灵丹妙药 | 关于缓存,你必须要知道的

这两天小编一直在总结缓存的要点,也同时参考了一些文档,仅此奉上,以供参考。 缓存是必备技能 身为后端开发的开发人员,缓存是必备技能。不需要花费太多的精力就能显著...

3307
来自专栏Bug生活2048

利用云开发优化博客小程序(二)——评论功能

这次迭代主要是完善了评论功能「不知道审核能不能过」,一开始觉得很快能搞定,然而真正开发的时候还是碰到很多问题,这篇文章既是回顾总结,也是记录下自己在开发过程中遇...

893
来自专栏Golang语言社区

关于缓存你需要知道的

About Cache 作后端开发的同学,缓存是必备技能。这是你不需要花费太多的精力就能显著提升服务性能的灵丹妙药。前提是你得知道如何使用它,这样才能够最大限度...

34113
来自专栏前端知识分享

第176天:页面优化

从用户角度而言,优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。从服务商角度而言,优化能够减少页面请求数、或者减小请求所占带宽...

572
来自专栏小詹同学

Python爬虫系列——入门到精通

这是小詹的第④篇免费分享,每一篇都是精挑细选! 老规矩,免费免转发,直接无条件分享给大家(方便的话点一下文章末尾广告就是对小詹的支持啦~)资源分享类的干货一般不...

3469
来自专栏风中追风

分布式基础__聊聊TCP传输的滑动窗口协议的演进

写这篇文章前,我有些肺腑之言想感谢一下我的微信好友“风大”。 是他给了我信心,原来没有很难的技术,只要你肯努力总能赶上其他人。 后来关注他的博客后,发现他尽然觉...

36615

扫码关注云+社区