您的位置:首页 > 编程语言 > C语言/C++

易用的C++ RPC服务框架 - pioneer - 5 - 技术实现:函数的序列化

2013-08-13 05:06 666 查看
在过去的两年时间里,我一直在用C++11写分布式数据库。在分布式系统中,远程方法调用是一个大麻烦。能不能像本地方法一样调用远程方法?

能不能以异步的方式调用远程方法而调用线程不阻塞?能不能广播调用远程方法?能不能自动将异常信息带到客户端来处理?

于是我写了一个RPC框架pioneer,支持以下特性:

1.针对集群来设计

2.和本地函数几乎一样的调用方法

3.没有IDL

4.支持同步调用和异步调用

5.支持有返回值/无返回值,对无返回值的调用进行优化

6.支持一对一、一对多、多播、广播、可靠广播调用

7.支持异常处理

8.不考虑跨语言

上一篇文章中,我介绍了在C++11中,如何借助std::tuple和可变模板参数来序列化一个函数,并且给出了一个通用实现和测试代码:

实现细节见:https://github.com/galaxyeye/atlas/blob/master/atlas/serialization/function.h

测试程序:https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/function.cpp

使用atlas::serialization::function,可以很轻松地将一个函数序列化,反序列化以及反序列化后执行。

其秘诀在于使用了三个威力强大的武器:std::tuple, std::function和可变模板参数。

那么有几个问题:

1. 如何序列化std::tuple?
标准库中并没用对std::tuple的输入输出实现,也就是说,标准库中没有实现:

template<typename... Args>
std::ostream& operator<<(std::ostream& os, const std::tuple<Args...>& t);


和:

template<typename... Args>
std::istream& operator>>(std::istream& is, std::tuple<Args...>& t);


boost::serialization中也没有对应实现。唉,得自己动手了。

2. 我们找到了序列化可变参数包的方法,就是引入一个中间变量std::tuple来保存这个参数包,那么我们知道函数function, 和一个参数包tuple,怎么样把tuple里的参数解出来供函数function调用?

对于第一个问题,通常有两种解决方法:第一种是{我的解决方法},另一种是~{我的解决方法}。

我的解决方法纯粹使用函数进行编译期模板推导,代码平白如话:

template<size_t idx, typename Archive, typename ... Elements>
void aux_serialize(Archive& ar, std::tuple<Elements...>& t, single_parameter_pack_tag) {
ar & std::get<idx>(t);
}

template<size_t idx, typename Archive, typename ... Elements>
void aux_serialize(Archive& ar, std::tuple<Elements...>& t, not_single_parameter_pack_tag) {
ar & std::get<idx>(t);

aux_serialize<idx + 1>(ar, t, atlas::is_last_parameter<idx, Elements...>());
}

template<typename Archive, typename ... Elements>
void serialize(Archive& ar, std::tuple<Elements...>& t, last_parameter_tag) {
ar & std::get<0>(t);
}

template<typename Archive, typename ... Elements>
void serialize(Archive& ar, std::tuple<Elements...>& t, not_last_parameter_tag) {
aux_serialize<0>(ar, t, std::false_type());
}


boost兼容的序列化函数:

namespace boost {
namespace serialization {

template<typename Archive, typename ... Elements>
Archive& serialize(Archive& ar, std::tuple<Elements...>& t, const unsigned int version) {
atlas::serialize(ar, t, atlas::is_single_parameter_pack<Elements...>());

return ar;
}

} // serialization
} // boost


完整的代码在这里。这里用到几个编译期函数:

is_single_parameter判断一个参数包是否为单个参数

is_last_parameter判断一个参数是否为参数包中最后一个参数

用这套方案,可以解决很多编译期问题,譬如打印出一个std::tuple,代码几乎一模一样。

其他的解决方案,一般是基于class的模板参数推导,不赘述。

对于第二个问题,facebook的folly提出来一个方案,apply_tuple。folly::apply_tuple是这么用的:

int x = atlas::apply_tuple(std::plus<int>(), std::make_tuple(12, 12));
assert(x == 24);


你可以看到,这就是给定一个函数,以及包含函数的参数的打包的std::tuple,怎么调用这个函数。所以在前面的文章里的function_wrapper的operator就可以这么实现:

template<typename Res, typename ... Args>
class function_wrapper<Res(Args...)> {
public:
Res operator()() const { return apply_tuple(_f, _args); }
public:
std::function<Res(Args...)> _f;
std::tuple<Args...> _args;
};


至此,一个可序列化的函数包装器就成型了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐