关于ZeroC Ice C++异步invoke,整合ProtoBuf对象

2024-04-15 11:48

本文主要是介绍关于ZeroC Ice C++异步invoke,整合ProtoBuf对象,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

导读

      • 前奏
      • 问题马上就来了
      • 没办法只能自己摸索了
        • 1. 先来看看其定义
        • 2. 参数的准备
        • 3. 准备回调函数
        • 4. 正式调用
        • 5. 到此客户端就完成了,服务端怎么办呢?
      • 现在大家喜欢用ProtoBuf,怎么来整合到Ice中

前奏

本文并不是一篇教程,只是提供了一个思路,最好先熟悉了Ice相关概念,动手调试运行了官方Demo后,再来看。

我们知道Ice的一般使用方式,是通过Slice文件生成的对应的类来操作,Ice为我们封装了网络通信的过程,只需调用Slice中定义类方法即可,如下Slice文件定义所示:

module Demo {interface AskPerson {string AskNumber(string sName);string AskName(string sName);};
};

客户端通过该类的Proxy直接调用该类的方,对于客户端来说Proxy就是代表着该类。我们可以按照如下的方式调用,也可以学习官方Demo。

// 从配置文件读取属性信息
Ice::InitializationData initData;
initData.properties = Ice::createProperties();
initData.properties->load("config.client");ic = Ice::initialize(argc, argv,initData);// 从配置文件中读取Ice::Object即我们的目标对象的标识号
Ice::ObjectPrx base = ic->propertyToProxy("AskPerson.Proxy");
AskPersonPrx askerPrx = AskPersonPrx::checkedCast(base);
if (!askerPrx)
{throw "Invalid proxy";
}std::cout<<"client get"<<askerPrx->AskName("xx")<<std::endl;

很简单,读一下Demo和网上的文章基本就能学会使用,但是可能这个技术有点Old,所以好多文章都是2012年之前的,也鲜有人讨论。

问题马上就来了

假设一个项目中有50个这样的对象,各个业务模块,难道都需要创建对象askPerson1~50?且万一类名改变了怎么办?在业务模块经常因为这样的变动而修改代码,肯定没有人会感觉到很舒服。

首先想到的是封装一层,这些生成的对象都有Ice基类的,但方法名是个麻烦事儿,Ice不可能在Ice::Object中提前定义好我们需要的虚函数。

幸好,Ice提供了invoker方法,我们只需提供函数名和参数,invoker会帮我们发起RPC调用。

同步方式调用invoker很简单,跟直接调AskName差不多,只是返回值,变成了输出参数,官方Demo也有比较简单的例子,就不赘述了。invoker定义如下:

bool ice_invoke(const ::std::string& operation,::Ice::OperationMode mode,const ::std::vector< ::Ice::Byte>& inParams,::std::vector< ::Ice::Byte>& outParams)

可奇怪的是,官方Demo中只讲了生成类AskPerson直接调成员方法的async,没有讲invoker的async。网上的文章也几乎没有讨论的。

没办法只能自己摸索了

直接上结论了,Ice提供了begin_ice_invoke,其实就是围绕它来完成异步的。我们按照begin_ice_invoke的要求,准备参数,准备回调函数。

1. 先来看看其定义
    ::Ice::AsyncResultPtr begin_ice_invoke(const ::std::string& operation,::Ice::OperationMode mode,const ::std::vector< ::Ice::Byte>& inParams, //RPC调用的参数const ::Ice::Context& __ctx,const ::Ice::CallbackPtr& __del,             //回调函数指针const ::Ice::LocalObjectPtr& __cookie = 0)

其中operation就是slice文件中定义的成员方法,例如下面代码中的GoNameAsync:

#ifndef ASKPERSON_ICE
#define ASKPERSON_ICE[["cpp:include:person.pb.h"]]
[["cpp:include:StreamProtobuf.h"]]module Demo {["cpp:type:PersonModule::Person"] sequence<byte> Person;interface AskPerson {string AskNumber(string sName);string AskName(string sName);["amd"] void GoNameAsync(string sName);["amd"] void GoNumberAsync(int nGid,string sName);["amd"] void GoPerson(Person p);};
};#endif

而inParams就是GoNameAsync的参数,只不过是序列化后的;Ice::CallbackPtr是一个回调函数的指针,用于异步的方式返回数据。

GoNameAsynce的返回值为void,服务器也可以返回一个序列化的数据,目前还不清楚内部实现。

2. 参数的准备

利用Ice提供的输入输出流,将数据直接序列化为buffer,如下代码所示,将字符串write到Ice的字节序列ByteSeq中,ByteSeq的实质是std::vector< ::Ice::Byte>

Ice::InitializationData initData;
initData.properties = Ice::createProperties();
initData.properties->load("config.client");//ic = Ice::initialize(argc, argv,initData);
ic = Ice::initialize(initData);...Ice::ByteSeq inParams;
Ice::OutputStreamPtr out = Ice::createOutputStream(ic);
out->startEncapsulation();
out->write("tom");
out->endEncapsulation();
out->finished(inParams);

上例我们向输出流里写入了一个字符串"tom",如果一个RPC调用有多个参数,只需多次调用out->write()方法,该方法重载了多种类型,我们传值就可以了。

最后直接将流里面的数据,刷新到缓冲区(Ice::ByteSeq字节序列)。

3. 准备回调函数

这里有点特殊,有参数类型要求,不是随便写一个函数指针就可以的,多写写就习惯了。

class CallBack :public IceUtil::Shared
{// 这个回调函数名不限制,但是参数类型要给正确void response(const Ice::AsyncResultPtr& p){Ice::ByteSeq datas;// 从invoke中获取字节序列p->getProxy()->end_ice_invoke(datas,p);// 通过输入流将该字节序,直接读取Ice::InputStreamPtr in = Ice::createInputStream(p->getCommunicator(), datas);// 具体的数据是哪种类型,要看服务端怎么发的std::string message;in->read(message);in->readPendingObjects();in->endEncapsulation();std::cout<<"get response"<<message<<std::endl;}
};

回调函数被调用后,我们首先获取字节序列,然后通过输入流,直接读取数据到对应的类型变量中。字节序列+流的形式,封装了解析细节,使用起来非常方便。

4. 正式调用

通过Ice::newCallback方法创建回调函数的指针。并发起async RPC调用。

CallbackPtr cb = new Callback();
// 这个context现在我还没用过,初次使用直接创建对象吧,不要犹豫
Ice::Context context;// CallbackPtr是ICE定义的,重点关注newCallback这个方法
Ice::CallbackPtr cbPtr = Ice::newCallback(cb, &Callback::response);// 正式发起RPC调用,输入参数,等待回调
Ice::AsyncResultPtr asyncPtr = base->begin_ice_invoke("AskNameAsync",Ice::Normal,inParams,context,cbPtr);
asyncPtr->waitForCompleted();
5. 到此客户端就完成了,服务端怎么办呢?

服务端可以采用同步方式处理(即使客户端采用异步),也可以采用异步(AMD模式),两端是独立的,这是很基本的概念。

服务端采用异步方式,开线程搞个workqueue是一般步骤,下面来看看具体的目标服务对象怎么定义的,由于是invoker方式,就不能搞一个类然后继承Demo::AskPerson了。要像下面这样:

class AskPersonInvokerAsync : public Ice::BlobjectAsync
{
public:AskPersonInvokerAsync(const WorkQueuePtr& workQueue):_workQueue(workQueue){}virtual void ice_invoke_async(const Ice::AMD_Object_ice_invokePtr& cb, const std::vector<Ice::Byte>&inParam, const Ice::Current& current){_workQueue->add(cb,inParam,current);}private:WorkQueuePtr _workQueue;
};

注意ice_invoke_async第一个参数(即回调函数的指针)的类型;WorkQueue就是官方Demo中的,直接拿来用的哈,流程就走完了。

现在大家喜欢用ProtoBuf,怎么来整合到Ice中

原理就是不用PB来序列化,只是用PB的对象,而采用Ice输入输出流,直接序列化PB对象到Ice::ByteSeq中。

https://github.com/zeroc-ice/ice-protobuf中有例子,但没有invoke的,我们把它的序列化头文件StreamProtobuf.h直接拿来用。

该文件中定义的模板StreamHelper是一个特化模板,将PB对象序列化的具体实现,Ice中也定义了这样的一个模板,如此一来,PB就可以很方便的整合进Ice的输入输出流,编译时会自动匹配这个特化模板,使用时像如下代码:

// 定义的一个PB类
PersonModule::Person testPerson;
testPerson.set_age(15);
testPerson.set_name("kyle");
...Ice::ByteSeq inParams;
Ice::OutputStreamPtr out = Ice::createOutputStream(ic);
out->startEncapsulation();// 注意通过输出流write对象时,记得填模板参数
out->write<PersonModule::Person>(testPerson);
out->endEncapsulation();
out->finished(inParams);

注意是用Ice::OutputStream来序列化,而不用PB来序列化

然后就可以传参,调用了。

这篇关于关于ZeroC Ice C++异步invoke,整合ProtoBuf对象的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/905729

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(