易用的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的输入输出实现,也就是说,标准库中没有实现:
和:
boost::serialization中也没有对应实现。唉,得自己动手了。
2. 我们找到了序列化可变参数包的方法,就是引入一个中间变量std::tuple来保存这个参数包,那么我们知道函数function, 和一个参数包tuple,怎么样把tuple里的参数解出来供函数function调用?
对于第一个问题,通常有两种解决方法:第一种是{我的解决方法},另一种是~{我的解决方法}。
我的解决方法纯粹使用函数进行编译期模板推导,代码平白如话:
boost兼容的序列化函数:
完整的代码在这里。这里用到几个编译期函数:
is_single_parameter判断一个参数包是否为单个参数
is_last_parameter判断一个参数是否为参数包中最后一个参数
用这套方案,可以解决很多编译期问题,譬如打印出一个std::tuple,代码几乎一模一样。
其他的解决方案,一般是基于class的模板参数推导,不赘述。
对于第二个问题,facebook的folly提出来一个方案,apply_tuple。folly::apply_tuple是这么用的:
你可以看到,这就是给定一个函数,以及包含函数的参数的打包的std::tuple,怎么调用这个函数。所以在前面的文章里的function_wrapper的operator就可以这么实现:
至此,一个可序列化的函数包装器就成型了。
能不能以异步的方式调用远程方法而调用线程不阻塞?能不能广播调用远程方法?能不能自动将异常信息带到客户端来处理?
于是我写了一个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; };
至此,一个可序列化的函数包装器就成型了。
相关文章推荐
- 易用的C++ RPC服务框架 - pioneer - 4 - 技术实现:函数的序列化
- 易用的C++ RPC服务框架 - pioneer - 2 - 编译和执行演示程序
- 易用的C++ RPC服务框架 - pioneer - 3 - 编写你自己的控制台命令
- 易用的C++ RPC服务框架 - pioneer - 1 - 设计目标和应用场景
- C++ 初始化函数的实现 - Ling Xu 的 C++ 技术随笔 - C++博客
- 魅族 C++ 微服务框架技术
- C++ 开发 Web 服务框架 - HTTP Web 框架的设计与实现(二)
- C++ 开发 Web 服务框架 - HTTP Web 框架的设计与实现(二)
- 【原创】自己动手实现RPC服务调用框架
- 采用Best effort 1pc + 回滚补偿机制实现的一个distributed transaction (分布式事务框架).基于dubbo rpc服务上实现。
- 自定义RPC通讯框架,实现dubbo远程RPC服务治理功能
- 魅族 C++ 微服务框架技术内幕揭秘
- 腾讯高性能RPC开发框架Tars实现服务治理(微服务)
- 硬件访问服务4之Android硬件访问服务框架及系统函数全详细实现
- 基于c++和linux的线程池技术及框架实现(网络下载,测试并修改其中可能的错误)
- 【远程调用框架】如何实现一个简单的RPC框架(三)优化一:利用动态代理改变用户服务调用方式
- 利用dwr框架来实现“服务推”技术的方法
- C++中的Thunk技术 / 非静态类成员函数作为回调函数 的实现方法
- CAF(C++ actor framework)(序列化之复杂类,分析 还有自己不懂的细思恐极函数实现)(三)
- C++中的Thunk技术 / 非静态类成员函数作为回调函数 的实现方法