共计 4209 个字符,预计需要花费 11 分钟才能阅读完成。
这几天看一个 rpc 库 (https://github.com/rpclib/rpc…,server 端的注册函数返回类型以及参数类型能够任意,client 端只有填充对应类型的参数即可实现调用:
Server:
#include <iostream>
#include "rpc/server.h"
void foo() {std::cout << "foo was called!" << std::endl;}
int main(int argc, char *argv[]) {
// Creating a server that listens on port 8080
rpc::server srv(8080);
// Binding the name "foo" to free function foo.
// note: the signature is automatically captured
srv.bind("foo", &foo);
// Binding a lambda function to the name "add".
srv.bind("add", [](int a, int b) {return a + b;});
// Run the server loop.
srv.run();
return 0;
}
Client:
#include <iostream>
#include "rpc/client.h"
int main() {
// Creating a client that connects to the localhost on port 8080
rpc::client client("127.0.0.1", 8080);
// Calling a function with paramters and converting the result to int
auto result = client.call("add", 2, 3).as<int>();
std::cout << "The result is:" << result << std::endl;
return 0;
}
这在 c ++ 中想要实现是不容易的,因为 c ++ 没有运行时反射,而 server 显然只有在运行时能力晓得 client 想要执行哪个函数。并且 server 如何存储注册的参数类型以及返回类型各不相同的函数呢? 尽管有 std::function,但函数参数类型和返回类型决定了这个函数的动态类型,c++ 容器寄存的对象必须具备雷同类型(std::any 相似机制不能解决这个问题,因为当你想要获取真正的类型时,仍然须要编译期就指定,换言之 c ++ 提供的类型变换都是在编译期指定,但 rpc 调用中只有在运行期能力取得类型信息,这是基本的矛盾)。
一次 rpc 调用须要以下步骤:
client 指定函数名称,以及各个参数 —–rpc 库将函数名称与各个参数序列化,通过网络发送给 server 端 —–server 承受到网络数据,反序列化失去调用函数的名称和参数序列 —– 通过函数名称找到注册的函数对象,并传递参数序列,调用对应函数,拿到函数返回值并序列化,通过网络发送给 client 端 —–client 收到网络数据,反序列化并返回给 rpcc 使用者。
利用可变参数列表将 client 端的函数名称和参数生成 std::tuple,std::tuple-> 二进制数据须要序列化库提供,失去序列化后的二进制数据,通过网络 io 发送给 server,server 同样通过序列化库将二进制数据反序列化为 std::tuple,利用 c ++14 的 std::integer_sequence 开展 std::tuple 作为函数参数实现调用(https://www.cnblogs.com/abeli…),利用 lambda 作类型擦除,记录不同的函数对象(之前看过一篇文章将 c ++ 的几种类型擦除办法,说 lambda 是最强的类型擦除办法,明天终于领会到什么意思了,参考:https://github.com/rpclib/rpc… 如何将不同类型的函数存储在同一动态类型中)。
上面是一些记录的尝试的代码,很乱,次要是尝试上述办法的应用:
#include <tuple>
#include <iostream>
#include <string>
#include <utility>
#include <unordered_map>
class Test {
public:
int age;
explicit Test(int a) : age(a) {}
Test(const Test& t) {
age = t.age;
std::cout << "copy" << std::endl;
}
Test(Test&& t) noexcept {
age = t.age;
std::cout << "move" << std::endl;
}
};
int method(Test&& a, const Test& b) {return a.age + b.age;}
std::string strappend(std::string str1, std::string str2) {std::string result(str1);
result.append(str2);
return result;
}
template<typename F, typename T, size_t... l>
decltype(auto) apply_impl(F& f, T&& t, std::index_sequence<l...>) {return f(std::forward<typename std::tuple_element<l, std::decay_t<T>>::type>(std::get<l>(std::forward<T>(t)))...);
}
template<typename F, typename T>
decltype(auto) apply(F& f, T&& tuple) {return apply_impl(f, std::forward<T>(tuple), std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>());
}
template<typename Rt, typename... Args>
struct func_traits {
using ReturnType = Rt;
using ArgsType = std::tuple<typename std::decay_t<Args>::type...>;
};
class elti {
public:
// just for test.
template<typename T>
T getElementAs(size_t i) {return T();
}
template<typename TupleType>
void Convert(TupleType& tuple) {ConvertInner<0>(tuple);
}
private:
template<size_t index, typename TupleType>
void ConvertInner(TupleType& tuple) {
using nthType = typename std::tuple_element<index, TupleType>::type;
nthType obj = getElementAs<nthType>(index);
std::get<index>(tuple) = std::move(obj);
ConvertInner<index + 1>(tuple);
}
};
template<typename TupleType>
class MyTupleElement {
template<typename Tp>
class TupleElementInner;
template<typename Arg, typename... ArgsTypes>
class TupleElementInner<std::tuple<Arg, ArgsTypes...>> : public TupleElementInner<std::tuple<ArgsTypes...>> {
public:
using BaseType = TupleElementInner<std::tuple<ArgsTypes...>>;
constexpr static size_t index = BaseType::index + 1;
template<typename Tp>
static void PrintElement(Tp& tuple) {std::cout << std::get<index>(tuple) << std::endl;
BaseType::PrintElement(tuple);
}
};
template<typename LastArg>
class TupleElementInner<std::tuple<LastArg>> {
public:
constexpr static size_t index = 0;
template<typename Tp>
static void PrintElement(Tp& tuple) {std::cout << std::get<index>(tuple) << std::endl;
}
};
public:
static void PrintTupleElement(TupleType& tuple) {TupleElementInner<TupleType>::PrintElement(tuple);
}
};
int main() {
using namespace std::literals::string_literals;
auto tu1 = std::make_tuple(Test(2), Test(3));
auto result1 = apply(method, tu1);
auto tu2 = std::make_tuple(std::string("chloro"), std::string("pn"));
//auto re2 = apply(strappend, tu2);
//std::cout << result1 << std::endl;
//std::cout << re2 << std::endl;
MyTupleElement<std::tuple<std::string, std::string>>::PrintTupleElement(tu2);
return 0;
}
通过开展 tuple 包到函数调用,参考 url 的那个比较简单,这里实现了具备完满转发能力的形式。