【C++】Boost.Asio+Serialization 建立RPC demo(客户端)

RPC服务(Remote Procedure Call),可以从客户端要求服务端进行相关业务,从服务端获取相应的答案,调用方法类似普通的函数调用。RPC在分布式系统中的系统环境建设和应用程序设计中有着广泛的应用,比如:分布式操作系统的进程间通讯;构造分布式计算的软件环境;远程数据库服务等。
这里尝试用Boost.Asio和Serialization搭建一个RPC demo,编译平台是VS 2017,需要用到C++11及以上标准。Asio负责开发异步服务端和客户端,Serialization用于传输函数和参数的序列化/反序列化。本文先介绍RPC 客户端的相关内容,客户端使用简单的同步模式开发。具体参见github:

统一调用函数声明

RPC客户端的核心在于建立一个统一的RPC调用函数形式,用以灵活调用函数。所有RPC方法通过call函数调用,具体函数调用通过函数名字符串来指代,后续跟随具体的参数。声明如下:

template<typename T, typename... Params>
Respond<T> Call(std::string funcName, Params... ps)

这是一个带有可变模板的函数,用以输入不同长度、不同类别的参数。返回值为Respond,便以统一返回类型。可调用函数的返回类型可能为void,我用int来替换,需要使用type_traits方法:

template<typename T>
struct type_transfer { typedef T type; };
template<>
struct type_transfer<void> { typedef int type; };

template<typename T>
struct Respond {
	Respond() { errorCode_ = 0; }
	int errorCode_;
	typename type_transfer<T>::type val_;
};
Call函数实现
template<typename T, typename... Params>
Respond<T> Call(std::string funcName, Params... ps) {
	memset(writeBuffer_, 0, 4096);//oa所用的缓冲
	memset(readBuffer_, 0, 4096);//ia所用的缓冲
	os.close();//重初始化
	os.open(sr);
	boost::archive::binary_oarchive oa(os);
	oa << funcName;
	//parameter number
	int N = int(sizeof...(Params));//参数个数
	oa << N;

	std::tuple<typename std::decay<Params>::type...> args = std::make_tuple(ps...);//将所有参数存入tuple
	PackageParams(oa, args);//对所有参数进行序列化

	sock_.write_some(boost::asio::buffer(writeBuffer_),ec_);//同步传输
	is.close();//重初始化
	is.open(device);
	sock_.read_some(boost::asio::buffer(readBuffer_), ec_);//同步读入
	Respond<T> res;
	boost::archive::binary_iarchive ia(is);
	ia >> res;//反序列化
	return res;
}

需要关注的方法有:

  1. template<typename… Params> sizeof…(Params),C++11标准新用法,可以获取参数个数,但是返回的数值不能直接当作模板。…符号在前面
  2. std::tuple<typename std::decay::type…>,将所有参数放入std::tuple,std::decay用于去掉const,&,&&模板符号。…符号在type后面。(这位置有点麻烦)
    此外,PackageParams是一个自定义函数用于展开参数并一一处理,下面来重点介绍。
展开参数列表并序列化

PackageParams函数定义如下

template<typename... Args>
void PackageParams(boost::archive::binary_oarchive &oa, const std::tuple<Args...>& t)
{
	PackageParamsImpl(oa, t, std::index_sequence_for<Args...>{});
}

typename… Args表示所有参数的模板。std::index_sequence_for<Args…>{}根据模板数量N,建立一个0~N的模板序列,注意这里的数字序列可以当作模板!
实际的操作函数为:

template<typename Tuple, std::size_t... Is>
void PackageParamsImpl(boost::archive::binary_oarchive &oa, const Tuple& t, std::index_sequence<Is...>)
{
	std::initializer_list<int>{ ((oa << std::get<Is>(t)), 0)... };
}

std::index_sequence_for<Args…>对应这个函数是std::size_t… Is,Is是模板数字序列,注意是模板。td::index_sequence_for生成的对象是std::index_sequence<Is…>,参考type_traits方法,这个对象没有实质内容,仅仅是把Is…模板数字序列引进来。
比较让人惊讶的是std::initializer_list的用法。这里直接展开了参数列表,使得oa对每个参数都进行了一次序列化操作。这个用法我在网上搜索,相关的内容很少。std::initializer_list{ ((oa << std::get(t)), 0)… };最后返回的是全为0值的列表,oa << std::get(t)返回的是一个binary_oarchive引用,然而这里仅仅保留0。如果用((oa << std::get(t)))或者(0, (oa << std::get(t)))都会报错。此外如果不写 < int >,也会报错。

调用方法
auto client = Client::New(service);
client->Start(ep);
auto res2 = client->Call<SVDData>(std::string("SVD"), mat2);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章