FFRPC应用之Client/Server

简介: 摘要: Ffrpc 进行了重构,精简了代码,代码更加清晰简洁,几乎完美的达到了我的预想。接下来将写几遍文章来介绍ffrpc可以做什么。简单总结ffrpc的特性是:  Ffrpc是c++ 网络通信库  全异步 + 回调函数 机制  支持普通二进制协议、protobuf、thrift ...

摘要:

Ffrpc 进行了重构,精简了代码,代码更加清晰简洁,几乎完美的达到了我的预想。接下来将写几遍文章来介绍ffrpc可以做什么。简单总结ffrpc的特性是:

  •  Ffrpc是c++ 网络通信库
  •  全异步 + 回调函数 机制
  •  支持普通二进制协议、protobuf、thrift
  •  基于Broker模式设计
  •  设计精巧,代码量小,核心ffrpc的代码只有1000行
  •  接口的性能监控是集成式的,使用者自动获得了接口性能数据,方便优化接口

普通二进制协议示例

Ffrpc实现了一个最基本的二进制序列化方法,基本的原理就是如果是固定长度那么就直接拷贝,如果是字符串,就先拷贝长度再拷贝内容。所以只支持向后扩展字段,对其他语言支持也不方便,但如果只是c++语言间传递消息,则显得非常的方便和高效。比如网游服务器中各个进程的通信可以采用这种最简单的二进制协议。Ffrpc中定义了一个工具类ffmsg_t来定义二进制消息.

消息定义:

struct echo_t
{
    struct in_t: public ffmsg_t<in_t>
    {
        void encode()
        {
            encoder() << data;
        }
        void decode()
        {
            decoder() >> data;
        }
        string data;
    };
    struct out_t: public ffmsg_t<out_t>
    {
        void encode()
        {
            encoder() << data;
        }
        void decode()
        {
            decoder() >> data;
        }
        string data;
    };
};

 

读者可以看到,ffmsg_t中提供了流式的序列化方法,使得序列化变得很容易。设计服务器消息的时候,需要注意的点有:

  •  在设计服务器接口的时候,每个接口接受一个消息作为参数,一个处理完毕返回一个消息,这是最传统的rpc模式。Ffrpc中采用这样的设计理念以简化和规范化接口设计。如果使用ffmsg_t定义消息,本人推荐的定义风格类似上面的代码这样。上面定义的是echo接口的输入消息和输出消息,但是都定义在echo_t结构内可以清晰的表明这是一对接口消息。
  •  传统的服务器接口会为每个接口定义一个cmd,然后通过cmd反序列化成特定的消息调用特定的接口,ffrpc省略了cmd的定义,而是直接采用消息名称作为cmd,比如在ffrpc中注册的接口接受echo_t的消息,那么收到echo_t的消息自然而言的是调用这个接口
  •  接口定义的时候必须的同时制定输入消息和输出消息
  •  Ffmsg_t支持普通类型,字符串类型、stl类型。

echo服务的实现代码:

 

struct echo_service_t
{
    //! echo接口,返回请求的发送的消息ffreq_t可以提供两个模板参数,第一个表示输入的消息(请求者发送的)
    //! 第二个模板参数表示该接口要返回的结果消息类型
    void echo(ffreq_t<echo_t::in_t, echo_t::out_t>& req_)
    {
        echo_t::out_t out;
        out.data = req_.msg.data;
        LOGINFO(("XX", "foo_t::echo: recv %s", req_.msg.data.c_str()));

        req_.response(out);
    }
};
echo_service_t foo;
    //! broker客户端,可以注册到broker,并注册服务以及接口,也可以远程调用其他节点的接口
    ffrpc_t ffrpc_service("echo");
    ffrpc_service.reg(&echo_service_t::echo, &foo);

    if (ffrpc_service.open(arg_helper))
    {
        return -1;
}

 

这样就定义了echo服务,echo服务提供了一个接口,接受echo_t::in_t消息,返回echo_t::out_t消息。由此可见使用ffrpc定义服务的步骤是:

  • l 定义消息和接口
  •  将接口注册到ffrpc的示例中,ffpc提供了reg模板方法,会自动的分析注册的接口使用神马输入消息,从而保证如果echo_t::in_t消息到来一定会调用对应的接口
  •  Ffrpc工作的核心是broker,简单描述broker的作用就是转发消息。Ffrpc的client和server是不直接连接的,而是通过broker转发消息进行通信。这样的好处是server的位置对于client是完全透明的,这也是broker模式最精髓的思想。所以ffrpc天生就是scalability的。Ffrpc的client比如要调用echo服务的接口,完全不需要知道serverr对应的位置或者配置,只需要知道echo服务的名字。有人可能担忧完全的broker转发可能会带来很大开销。Broker保证了消息转发的最佳优化,如果client或者server和broker在同一进程,那么消息直接是内存间传递的,连序列化都不需要做,这也是得益于broker模式,broker模式的特点就是拥有很好的scalability。这样无论是简单的设计一个单进程的server还是设计成多进程分布式的一组服务,ffrpc都能完美胜任。

调用echo服务的client示例:

struct echo_client_t
{
    //! 远程调用接口,可以指定回调函数(也可以留空),同样使用ffreq_t指定输入消息类型,并且可以使用lambda绑定参数
    void echo_callback(ffreq_t<echo_t::out_t>& req_, int index, ffrpc_t* ffrpc_client)
    {
        if (req_.error())
        {
            LOGERROR(("XX", "error_msg <%s>", req_.error_msg()));
            return;
        }
        else if (index < 10)
        {
            echo_t::in_t in;
            in.data = "helloworld";
            LOGINFO(("XX", "%s %s index=%d callback...", __FUNCTION__, req_.msg.data.c_str(), index));
            sleep(1);
            ffrpc_client->call("echo", in, ffrpc_ops_t::gen_callback(&echo_client_t::echo_callback, this, ++index, ffrpc_client));
        }
        else
        {
            LOGINFO(("XX", "%s %s %d callback end", __FUNCTION__, req_.msg.data.c_str(), index));
        }
    }
};
ffrpc_t ffrpc_client;
    if (ffrpc_client.open(arg_helper))
    {
        return -1;
    }
    
    echo_t::in_t in;
    in.data = "helloworld";
    echo_client_t client;
  ffrpc_client.call("echo", in, ffrpc_ops_t::gen_callback(&echo_client_t::echo_callback, &client, 1, &ffrpc_client));

 

使用ffrpc调用远程接口,只需要制定服务名和输入消息,broker自动定位echo服务的位置,本示例中由于ffrpc的client和server在同一进程,那么自动通过内存间传递,如果server和broker在同一进程,而client在其他进程或者物理机上,则broker和server之间的传递为内存传递,broker和client的消息传递为tcp传输,这就跟自己写一个tcp的server收到消息投递给service的接口,然后将消息再通过tcp投递给client。但是必须看到,ffrpc完全简化了tcp server定义,并且更加scalability,甚至完全可以用来进程内多线程的通讯。

需要注意的是,ffrpc拥有良好的容错能力,如果服务不存在或者接口不存在或者异常等发生回调函数仍然是会被调用,并且返回错误信息,从而使错误处理变得更加容易。比如游戏服务器中client登入gate但是scene可能还没有启动的时候,这里就能够很好的处理,回调函数检查错误就可以了。对于回调函数,对于经常使用多线程和任务队列的开发者一定非常熟悉,回调函数支持lambda参数应该算是锦上添花,使得异步的代码变得更加清晰易懂。

Broker的启动方式:

 

  int main(int argc, char* argv[])
  {
      //! 美丽的日志组件,shell输出是彩色滴!!
      LOG.start("-log_path ./log -log_filename log -log_class XX,BROKER,FFRPC -log_print_screen true -log_print_file false -log_level 3");
  
      if (argc == 1)
      {
          printf("usage: %s -broker tcp://127.0.0.1:10241\n", argv[0]);
          return 1;
      }
      arg_helper_t arg_helper(argc, argv);
  
      //! 启动broker,负责网络相关的操作,如消息转发,节点注册,重连等
  
      ffbroker_t ffbroker;
      if (ffbroker.open(arg_helper))
      {
          return -1;
      }
  
      sleep(1);
      if (arg_helper.is_enable_option("-echo_test"))
      {
          run_echo_test(arg_helper);        
      }
      else if (arg_helper.is_enable_option("-protobuf_test"))
      {
          run_protobuf_test(arg_helper);
      }
      else
      {
          printf("usage %s -broker tcp://127.0.0.1:10241 -echo_test\n", argv[0]);
          return -1;
      }
  
      ffbroker.close();
      return 0;
  }

 

Ffrpc中两个关键的组件broker和rpc,broker负责转发和注册服务器,rpc则代表通信节点,可能是client可能是server。即使是多个服务器,只需要broker一个监听的端口,其他的服务只需要提供不同的服务名即可。

Protobuf协议示例

Ffrpc 良好的设计抽离了对于协议的耦合,使得支持protobuf就增加了10来行代码。当然这也是由于protobuf生成的消息都继承message基类。当我实现thrift的时候,事情就稍微麻烦一些,thrift生成的代码更加简洁,但是生成的消息不集成基类,需要复制粘贴一些代码。

Protobuf的定义文件:

package ff;

message pb_echo_in_t {
  required string data = 1;
}
message pb_echo_out_t {
  required string data = 1;
}

 

我们仍然设计一个echo服务,定义echo接口的消息,基于ffrpc的设计理念,每个接口都有一个输入消息和输出消息。

Echo服务的实现代码:

struct protobuf_service_t
{
    //! echo接口,返回请求的发送的消息ffreq_t可以提供两个模板参数,第一个表示输入的消息(请求者发送的)
    //! 第二个模板参数表示该接口要返回的结果消息类型
    void echo(ffreq_t<pb_echo_in_t, pb_echo_out_t>& req_)
    {
        LOGINFO(("XX", "foo_t::echo: recv data=%s", req_.msg.data()));
        pb_echo_out_t out;
        out.set_data("123456");
        req_.response(out);
    }
};
protobuf_service_t foo;
    //! broker客户端,可以注册到broker,并注册服务以及接口,也可以远程调用其他节点的接口
    ffrpc_t ffrpc_service("echo");
    ffrpc_service.reg(&protobuf_service_t::echo, &foo);

    if (ffrpc_service.open(arg_helper))
    {
        return -1;
    }

 

跟使用ffmsg_t的方式几乎是一样的,ffreq_t 的msg字段是输入的消息。

调用echo服务器的client的示例代码

 

struct protobuf_client_t
{
    //! 远程调用接口,可以指定回调函数(也可以留空),同样使用ffreq_t指定输入消息类型,并且可以使用lambda绑定参数
    void echo_callback(ffreq_t<pb_echo_out_t>& req_, int index, ffrpc_t* ffrpc_client)
    {
        if (req_.error())
        {
            LOGERROR(("XX", "error_msg <%s>", req_.error_msg()));
            return;
        }
        else if (index < 10)
        {
            pb_echo_in_t in;
            in.set_data("Ohnice");
            LOGINFO(("XX", "%s data=%s index=%d callback...", __FUNCTION__, req_.msg.data(), index));
            sleep(1);
            ffrpc_client->call("echo", in, ffrpc_ops_t::gen_callback(&protobuf_client_t::echo_callback, this, ++index, ffrpc_client));
        }
        else
        {
            LOGINFO(("XX", "%s %d callback end", __FUNCTION__, index));
        }
    }
};

    ffrpc_t ffrpc_client;
    if (ffrpc_client.open(arg_helper))
    {
        return -1;
    }
    
    protobuf_client_t client;
    pb_echo_in_t in;
    in.set_data("Ohnice");

  ffrpc_client.call("echo", in, ffrpc_ops_t::gen_callback(&protobuf_client_t::echo_callback, &client, 1, &ffrpc_client));

 

Protobuf的优点是:

  •  支持版本,这样增加字段变得更加容易
  •  Protobuf是支持多语言的,这样可以跟其他的语言也可以通讯

Thrift协议的示例

Thrift 定义文件:

namespace cpp ff  

struct echo_thrift_in_t {      
  1: string data
}

struct echo_thrift_out_t {      
  1: string data
}

 

Thrift 的服务器实现代码:

 

struct thrift_service_t
{
    //! echo接口,返回请求的发送的消息ffreq_t可以提供两个模板参数,第一个表示输入的消息(请求者发送的)
    //! 第二个模板参数表示该接口要返回的结果消息类型
    void echo(ffreq_thrift_t<echo_thrift_in_t, echo_thrift_out_t>& req_)
    {
        LOGINFO(("XX", "foo_t::echo: recv data=%s", req_.msg.data));
        echo_thrift_out_t out;
        out.data = "123456";
        req_.response(out);
    }
};
thrift_service_t foo;
    //! broker客户端,可以注册到broker,并注册服务以及接口,也可以远程调用其他节点的接口
    ffrpc_t ffrpc_service("echo");
    ffrpc_service.reg(&thrift_service_t::echo, &foo);

    if (ffrpc_service.open(arg_helper))
    {
        return -1;
    }
    
    ffrpc_t ffrpc_client;
    if (ffrpc_client.open(arg_helper))
    {
        return -1;
}

 

调用 echo的client的示例:

 

struct thrift_client_t
{
    //! 远程调用接口,可以指定回调函数(也可以留空),同样使用ffreq_t指定输入消息类型,并且可以使用lambda绑定参数
    void echo_callback(ffreq_thrift_t<echo_thrift_out_t>& req_, int index, ffrpc_t* ffrpc_client)
    {
        if (req_.error())
        {
            LOGERROR(("XX", "error_msg <%s>", req_.error_msg()));
            return;
        }
        else if (index < 10)
        {
            echo_thrift_in_t in;
            in.data = "Ohnice";
            LOGINFO(("XX", "%s data=%s index=%d callback...", __FUNCTION__, req_.msg.data, index));
            sleep(1);
            ffrpc_client->call("echo", in, ffrpc_ops_t::gen_callback(&thrift_client_t::echo_callback, this, ++index, ffrpc_client));
        }
        else
        {
            LOGINFO(("XX", "%s %d callback end", __FUNCTION__, index));
        }
    }
};
ffrpc_t ffrpc_client;
    if (ffrpc_client.open(arg_helper))
    {
        return -1;
    }
    
    thrift_client_t client;
    echo_thrift_in_t in;
    in.data = "Ohnice";

ffrpc_client.call("echo", in, ffrpc_ops_t::gen_callback(&thrift_client_t::echo_callback, &client, 1, &ffrpc_client));

 

Thrift的优缺点:

  •  Thrift 更加灵活,支持list和map,而且可以嵌套
  •  支持N种语言
  •  官方的版本需要依赖boost,ffrpc从中提取出一个最基本的c++版本,只有头文件,不依赖boost

总结

  •  Ffrpc是基于c++的网络通讯库,基于broker模式scalability和 易用性是最大的优点
  •  使用ffrpc进行进程间通讯非常的容易,定义服务和接口就行了,你除了使用ffmsg_t最传统的消息定义,也可以使用google protobuf和facebook thrift。
  •  Ffrpc是全异步的,通过回调函数+lambda方式可以很容易操作异步逻辑。
  •  Ffrpc 接下来会有更多的示例,当系统复杂时,ffrpc的优势将会更加明显。
  •  Github的地址:https://github.com/fanchy/FFRPC

 

 

目录
相关文章
|
3月前
|
数据采集 缓存 移动开发
APP 用户行为数据治理实践:从事件口径管控到增长分析链路设计
本文从 APP 事件口径、双模采集、OneID 身份归一和漏斗留存分析出发,讨论用户行为数据治理与增长分析链路的建设方法。
|
3月前
|
人工智能 数据处理 开发工具
阿里云大模型Night Plan活动:夜间专属折扣,Qoder/Meoo客户使用Qwen3.7-Max享2折
阿里云推出大模型Night Plan夜间专属优惠活动,面向Qoder和Meoo全量用户,北京时间每晚22:00至次日8:00错峰时段内,调用Qwen3.7-Max享2折、Qwen3.7-Plus享4折,最高节省80%调用成本。用户无需额外操作,切换指定模型提交任务即可自动适配折扣,覆盖Qoder全系产品与秒悟Meoo平台。活动配套QoderWork CN首月0元福利、多档位灵活订阅方案,以及AI Coding入门/专业版组合套餐,一站式配齐编程工具与算力资源,大幅降低复杂代码项目、长文本推理、创意开发等高算力任务的运行成本,助力个人开发者与小团队零门槛接入旗舰大模型生产力。
|
4月前
|
数据采集 存储 人工智能
现场数据如何成为模型 “养料”:数据闭环训练实战与价值变革
AI落地产业的最大瓶颈不是算法,而是数据“水土不服”。公开数据难掩真实场景的复杂性,唯有源自一线的现场数据——带着噪声、异常与业务规则——才是模型持续进化的“原生养料”。构建采集、标注、训练、灰度部署到反馈回流的全链路数据闭环,方能实现模型准确率跃升、迭代成本下降与业务敏捷响应,让AI真正扎根产线、自我进化。(239字)
|
4月前
|
人工智能 自然语言处理 文字识别
2026年阿里云最新活动参考:云服务器、AI产品、优惠券、免费试用等热门活动介绍
2026年阿里云热门活动涵盖云服务器、AI产品及优惠券三大类。云服务器推出轻量应用服务器限时抢购及“99计划”长效特惠,支持续费同价,降低长期成本。AI产品方面,Qwen3.7-Max限时5折,百炼Token Plan及AI通用型节省计划最高享5.3折,助力智能升级。同时提供618满减券、百炼先用后返券及OPC百万Token补贴。此外,超160款云产品及30余款AI产品支持免费试用,含7000万大模型tokens额度,方便用户零成本验证方案。
|
6月前
|
机器学习/深度学习 人工智能 算法
普通摄像头秒变“透视仪”:黎曼分形透镜如何让微弱瑕疵无处遁形(军工项目之外研究)
一种基于黎曼分形动力学的非线性图像增强技术——“分形透镜”。无需AI模型,仅用纯C++实现,通过递归映射与黄金分割比调控,实时放大微弱灰度差异(如水渍、指纹、低温差目标),在普通USB摄像头上实现“透视级”细节增强,计算耗时 0.5ms,已开源并验证于工业检测与国防场景。
499 10
|
11月前
|
缓存 自然语言处理 供应链
1688 商品详情 API 与淘宝 / 速卖通同类接口差异对比:场景适配与选型建议
1688商品详情API与淘宝/速卖通同类接口差异对比及选型建议
|
7月前
|
机器学习/深度学习 传感器 人工智能
物理AI如何开始重塑我们的世界
本文提出AI驱动数字孪生的统一四阶段框架:建模(物理信息AI)、镜像(生成式AI实时同步)、干预(预测与优化)和自主管理(大模型与智能体)。综述十一大领域应用,剖析可扩展性、可解释性等共性挑战,指明跨学科演进方向。(239字)
873 3
|
12月前
|
存储 运维 Linux
阿里云渠道商:阿里云 NAS 的常见问题有哪些?
阿里云NAS常见问题解析:涵盖创建、挂载、权限、性能等十大场景,提供精准解决方案。合理配置可降本30%以上,提升业务连续性。附最佳实践,助力高效运维。
|
机器学习/深度学习 编解码 运维
通过短时倒谱(Cepstrogram)计算进行时-倒频分析研究(Matlab代码实现)
通过短时倒谱(Cepstrogram)计算进行时-倒频分析研究(Matlab代码实现)
443 0
|
人工智能 编解码 搜索推荐
AI智能换背景,助力电商图片营销升级
电商产品图换背景是提升销量与品牌形象的关键。传统抠图耗时费力,AI技术则实现一键智能换背景,高效精准。本文详解燕雀光年AI全能设计、Canva、Remove.bg等十大AI工具,涵盖功能特点与选型建议,助力商家快速打造高质量、高吸引力的商品图,提升转化率与品牌价值。(238字)
1053 0