ToplingDB 的 分布式 Compact 中 Client-Server 交互,使用了 topling-zip 中的序列化框架,该序列化框架初版完成于 2006 年,随后基于该序列化框架,实现了一个 RPC 系统。
后来整个库命名为 febird 库在 google code 上开源,再后来 google code 停止服务,febird 迁移到 github,有段时间重命名为 nark,之后重命名为 terark,目前 topling-zip 中代码的 namespace 仍是 terark。从 2006 年至今,除 namespace 名称之外,该序列化框架的接口一直保持稳定,2016 年的时候,针对 C++11 进行了模板推导相关的大幅优化,但仍保持了接口的稳定。
以下为原文正文,排版有轻微改动。
用 C++ 的高级模版特性实现一个不需要 IDL 的 RPC
作者: rockeet
发表日期: 2006年12月09日
分类: C++序列化 评论: 4 条 阅读次数: 4,053 次
- 相关文章1:ToplingDB 的序列化框架:简介
- 相关文章2:ToplingDB 的序列化框架:性能
当然,首要的是为了有用,其次是挑战自己驾驭 C++ 的能力,目前已经全部完成,并且取得了非常好的效果。使用该 RPC 的简短示例代码:
(一)接口定义
// sample usage...
// test.h
typedef std::vector<unsigned> vint_vec;
class AsyncInterface : public GlobaleScope {
public:
BEGIN_RPC_ADD_MF(AsyncInterface)
RPC_ADD_MF(get_val)
RPC_ADD_MF(get_len)
RPC_ADD_MF(squareVec)
RPC_ADD_MF(multiVec)
END_RPC_ADD_MF()
RPC_DECLARE_MF(get_val, (rpc_in<int> x))
RPC_DECLARE_MF(get_len, (const std::string& x))
RPC_DECLARE_MF(squareVec, (vint_vec& x))
RPC_DECLARE_MF(multiVec, (vint_vec& z, vint_vec& x, vint_vec& y))
};
RPC_TYPEDEF_PTR(AsyncInterface);
// more convenient way than AsyncInterface...
BEGIN_RPC_INTERFACE(SampleRPC_Interface2, SessionScope)
RPC_ADD_MF(get_val)
RPC_ADD_MF(get_len)
END_RPC_ADD_MF()
RPC_DECLARE_MF(get_val, (rpc_in<int> x))
RPC_DECLARE_MF(get_len, (const std::string& x))
END_RPC_INTERFACE()
(二)客户端
该客户端代码展示了同步和异步 RPC 调用,同步的更简单。
// async_client.cpp
#include <iostream>
#include <febird/rpc/client.h>
#include <febird/io/SocketStream.h>
using namespace std;
using namespace febird;
using namespace febird::rpc;
#include "../test.h"
void printVec(const vint_vec& vec) {
for (int i = 0; i != vec.size(); ++i)
cout << "vec[" << i << "]=" << vec[i] << "/n";
}
class AsyncImpl : public AsyncInterface {
public:
AsyncImpl() {
this->multiVec.set_async_callback(&AsyncImpl::on_multiVec);
}
private:
void on_multiVec(const client_packet_base& packet, vint_vec& z, vint_vec& x, vint_vec& y) {
printf("AsyncImpl::on_multiVec/n");
printf("ret=%u, z=%u, x=%u, y=%u/n", packet.retv, z.size(), x.size(), y.size());
}
};
RPC_TYPEDEF_PTR(AsyncImpl);
int main(int argc, char* argv[]) try {
auto_ptr<SocketStream> cs(ConnectSocket("127.0.0.1:8001"));
rpc_client<PortableDataInput, PortableDataOutput> client(cs.get());
AsyncImplPtr obj1;
SampleRPC_Interface2Ptr obj2;
client.create(obj1, "obj1");
client.create(obj2, "obj2");
int ret;
ret = client.retrieve(obj2, "obj2");
rpc_ret_t val = obj1->get_val(100);
cout << "obj1->get_val(100)=" << val << "/n";
val = obj1->get_len("hello, world!");
cout << "obj1->get_len(/"hello, world!/")=" << val << "/n";
std::vector<unsigned> vec;
vec.push_back((1));
vec.push_back((2));
vec.push_back((3));
vec.push_back((4));
vec.push_back((11));
vec.push_back((22));
vec.push_back((33));
vec.push_back((44));
val = obj1->squareVec(vec);
printVec(vec);
val = obj1->squareVec(vec);
printVec(vec);
std::vector<unsigned> vec2;
for (int i = 0; i != vec.size(); ++i) {
vec2.push_back(i + 1);
}
for (int i = 0; i < 5; ++i) {
std::vector<unsigned> vec3;
obj1->multiVec.async(vec3, vec, vec2);
cout << "obj1->multiVec(vec3, vec, vec2) = " << val << "/n";
printVec(vec3);
}
client.wait_pending_async();
return 0;
}
catch (const std::exception& exp) {
printf("exception: what=%s/n", exp.what());
return 1;
}
(三)服务端
// async_server.cpp
#include <febird/rpc/server.h>
#include <febird/io/SocketStream.h>
#include <iostream>
using namespace febird;
using namespace febird::rpc;
#include "../test.h"
// rpc 函数的返回值是 rpc_ret_t,主要是为了避免专门为 void 返回值的函数编写特化代码
// use macro for convenient
BEGIN_RPC_IMP_INTERFACE(SampleRPC_Imp1, AsyncInterface)
rpc_ret_t get_val(rpc_in<int> x) {
std::cout << "AsyncInterface::get_val(rpc_in<int> x=" << x.r << ")/n";
return x.r;
}
rpc_ret_t get_len(const std::string& x) {
std::cout << "AsyncInterface::get_len(const std::string& x=/"" << x << "/")/n";
return x.size();
}
rpc_ret_t squareVec(vint_vec& x) {
for (vint_vec::iterator i = x.begin(); i != x.end(); ++i) {
*i *= *i;
}
return x.size();
}
rpc_ret_t multiVec(vint_vec& z, vint_vec& x, vint_vec& y) {
z.clear();
for (int i = 0; i != x.size(); ++i) {
z.push_back(x[i] * y[i]);
}
return 0x12345678;
}
END_RPC_IMP_INTERFACE()
// don't use macro for more control
class SampleRPC_Imp2 : public SampleRPC_Interface2 {
rpc_ret_t get_val(rpc_in<int> x) {
std::cout << BOOST_CURRENT_FUNCTION << "x=" <<x.r << "/n";
return x.r;
}
rpc_ret_t get_len(const std::string& x) {
std::cout << BOOST_CURRENT_FUNCTION << "x=" << x << "/n";
return x.size();
}
public:
// if use macro, such as SampleRPC_Imp1, app can not use custom create
static remote_object* create() {
SampleRPC_Imp2* p = new SampleRPC_Imp2;
// set p ...
return p;
}
};
int main(int argc, char* argv[]) {
try {
SocketAcceptor acceptor("0.0.0.0:8001");
rpc_server<PortableDataInput, PortableDataOutput> server(&acceptor);
// register rpc implementation class...
RPC_SERVER_AUTO_CREATE(server, SampleRPC_Imp1);
server.auto_create((SampleRPC_Imp2*)0, &SampleRPC_Imp2::create);
server.start();
}
catch (const std::exception& exp) {
printf("exception: what=%s/n", exp.what());
}
return 0;
}
(四)解释说明
主要思路就是在 Server 端,用宏生成函数声明和工厂代码,把类注册到工厂中,启动server后,就可以自动接收client端的函数调用了。client发来的函数调用请求会派发到server端的用户定义函数体。当然,client端没有用户定义的函数体。
在client端,同样名字的宏,生成了和server端不同的代码,这些代码生成stub函数,把函数调用的参数序列化到网络。
其中主要使用了 boost::tuple,和 boost::any,来生成stub的虚函数。
这个实验很有趣,它说明了 C++ 强大的表达能力,即使没有 IDL,仅使用语言本身就可以实现 RPC。