RPC框架

智能 RPC框架 (C++)

RPC中文叫远程函数调用,它是一种通信方式,只是看起来像普通的函数调用。

它包括三个基本要素:

1:服务端注册相应的(服务)函数(用于调用方调用)

2:调用方通过函数调用的方式将一些信息和参数打包到消息,然后发送消息给被调用方。

3:被调用方收到消息后,提取信息和参数。调用相应函数。

 

被调用方不需要用户手动解析参数,而是由"包装代码"预先解析出来。

 

目前很多rpc框架都(设计)配有协议描述文件,通过代码生成,产生((含有)"包装代码")服务端的服务类或函数。

 

我不喜欢代码生成,我喜欢直接在代码中搞定它。

果然,我最近看到有朋友在一些脚本语言中做到这点。某些实现还不需要手动(预先)注册服务函数。

比如:

https://github.com/sniperHW/distri.lua/blob/master/examples/rpcserver.lua

https://github.com/akirayu101/GM_RPC/blob/master/gmrpc_lua/rpc_handlers/rpc_handler_sample.lua

https://github.com/akirayu101/GM_RPC/blob/master/gmrpc_py/rpc_handlers/rpc_handler_sample.py

 

然而我又不熟悉lua或python,所以我用C++11 来实现了它。

主要功能:

1:注册服务函数

1

2

3

4

5

6

void test5(string a, int b, map<int, map<int, string>> vlist)

{

}

 

rpc rpc_server; /*rpc服务器*/

rpc_server.def("test5", test5);

 

2:客户端调用远程函数

1

2

3

4

5

rpc rpc_client; /*rpc客户端*/

rpc_client.call("test5", "a", 1, mlist, [&upvalue](int a, int b){

    upvalue++;

    cout << "upvalue:" << upvalue << ", a:" << a << ", b:" << b << endl;

});

 

其中mlis是一个map<int,map<int,string>>类型变量。 rpc_client.call 的返回值是一个string,它表示此次call的消息。

我们可以把它(string 消息)通过网络发送给服务器。在这里(测试)我们直接通过下面的方式传递给服务端。

 

!!!注意!!!:call的最后一个参数可以是一个lambda,它表示处理此rpc返回值。 如果不是一个lambda,则它也是rpc调用参数。

 

3:服务端处理rpc request

1

rpc_server.handleRpc(rpc_request_msg);

 

其中 rpc_request_msg为接受到的网络消息(字符串)。

这样就会自动调用到我们的 test5 函数。 并且形参已经(自动)准备OK。你只需要在test5 里使用这些参数即可。(不用关心网络消息协议)。

 

4:被调用方可以返回数据给调用方

1

2

rpc_response_str = rpc_server.reply(1, 1, 2);   /* (1,1,2)中的1为调用方的req_id, (1,2)为返回值 */

rpc_client.handleResponse(rpc_response_str);

 

上面代码通过 rpc_server.reply返回消息给客户端。 然后客户端模拟收到消息后通过 rpc_client.handleResponse(rpc_response_str)

会回调rpc_client.call() 时 所传递的lambda回调函数。

 

注意:以上 服务函数(譬如test5)和rpc 返回值处理函数(譬如那个lambda)的参数 是任意个数,且"任意"类型

(支持 int,string,JsonObject-json对象,vector<int>,vector<string>, map<int,string>,map<string,int>,map<string,string>, map<int/string, 前述所有类型/递归> )

 

整个测试代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

void test1(int a, int b)

{

    cout << "in test1" << endl;

    cout << a << ", " << b << endl;

}

 

void test2(int a, int b, string c)

{

    cout << "in test2" << endl;

    cout << a << ", " << b << ", " << c << endl;

}

 

void test3(string a, int b, string c)

{

    cout << "in test3" << endl;

    cout << a << ", " << b << ", " << c << endl;

}

 

void test4(string a, int b)

{

    cout << "in test4" << endl;

    cout << a << "," << b <<  endl;

}

 

void test5(string a, int b, map<int, map<int, string>> vlist)

{

}

 

void test6(string a, int b, map<string, int> vlist)

{

}

 

void test7()

{

    cout << "in test7" << endl;

}

 

int main()

{

    int upvalue = 10;

    using namespace dodo;

 

    rpc rpc_server; /*rpc服务器*/

    rpc rpc_client; /*rpc客户端*/

 

    rpc_server.def("test4", test4);

    rpc_server.def("test5", test5);

    rpc_server.def("test7", test7);

 

    string rpc_request_msg; /*  rpc消息   */

    string rpc_response_str;       /*  rpc返回值  */

 

    {

        rpc_request_msg = rpc_client.call("test7");

 

        rpc_server.handleRpc(rpc_request_msg);

    }

 

    map<int, string> m1;

    m1[1] = "Li";

    map<int, string> m2;

    m2[2] = "Deng";

    map<int, map<int, string>> mlist;

    mlist[100] = m1;

    mlist[200] = m2;

 

    {

        rpc_request_msg = rpc_client.call("test5", "a", 1, mlist, [&upvalue](int a, int b){

            upvalue++;

            cout << "upvalue:" << upvalue << ", a:" << a << ", b:" << b << endl;

        });

 

        rpc_server.handleRpc(rpc_request_msg);

    }

 

    {

        rpc_request_msg = rpc_client.call("test5", "a", 1, mlist, [&upvalue](string a, string b, int c){

            upvalue++;

            cout << "upvalue:" << upvalue << ", a:" << a << ", b:" << b << ", c:" << c << endl;

        });

 

        rpc_server.handleRpc(rpc_request_msg);

    }

 

    {

        rpc_request_msg = rpc_client.call("test4", "a", 1);

        rpc_server.handleRpc(rpc_request_msg);

    }

     

    /*  模拟服务器通过reply返回数据给rpc client,然后rpc client处理收到的rpc返回值 */

    {

        rpc_response_str = rpc_server.reply(1, 1, 2);   /* (1,1,2)中的1为调用方的req_id, (1,2)为返回值 */

        rpc_client.handleResponse(rpc_response_str);

    }

 

    {

        rpc_response_str = rpc_server.reply(2, "hello", "world", 3);

        rpc_client.handleResponse(rpc_response_str);

    }

 

    cin.get();

    return 0;

}

 

RPC"框架"代码地址: https://github.com/IronsDu/accumulation-dev/blob/master/utils/rpc_test.cpp 。

欢迎讨论。

 

---update:

我们来看看最新战果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

class Player : public dodo::rpc

{

public:

    Player()

    {

        registerHandle("player_attack", &Player::attack);

        registerHandle("player_hi", &Player::hi);

    }

 

private:

    template<typename... Args>

    void        registerHandle(string name, void (Player::*callback)(Args...))

    {

        def(name.c_str(), [this, callback](Args... args){

            (this->*callback)(args...);

        });

    }

 

private:

    void    attack(string target)

    {

        cout << "attack:" << target << endl;

    }

 

    void    hi(string i, string j)

    {

        cout << i << j << endl;

    }

};

 

Player rpc_server; /*rpc服务器*/

Player rpc_client; /*rpc客户端*/

 

rpc_request_msg = rpc_client.call("player_attack", "Li Lei");

rpc_server.handleRpc(rpc_request_msg);

rpc_request_msg = rpc_client.call("player_hi", "Hello", "World");

rpc_server.handleRpc(rpc_request_msg);

 

 是不是非常简单呢?很轻松的把消息(参数)自动派发到实际的业务函数里?

每一个Player就是一个rpc,再结合网络库,就能很轻松的开发业务逻辑。

 

 

====================================================================================================================================================================================================================================================================================================================================================================

 

C++ RPC(远程过程调用)

 

目的

最近由于摩尔定律已经不太适用,随着大数据、计算量不断增加,导致单机处理能力不能满足需求,所以需要分布式计算,这就需要RPC(远程过程调用),下面简单介绍一下这个demo,来自于GitHub上的一个项目
csdn源码下载

client代码

#include <string>
#include <iostream>
#include <ctime>
#include "buttonrpc.hpp"

#ifdef _WIN32
#include <Windows.h>  // use sleep
#else
 #include <unistd.h>
#endif


#define buttont_assert(exp) { \
	if (!(exp)) {\
		std::cout << "ERROR: "; \
		std::cout << "function: " << __FUNCTION__  << ", line: " <<  __LINE__ << std::endl; \
		system("pause"); \
	}\
}\


struct PersonInfo
{
	int age;
	std::string name;
	float height;

	// must implement
	friend Serializer& operator >> (Serializer& in, PersonInfo& d) {
		in >> d.age >> d.name >> d.height;
		return in;
	}
	friend Serializer& operator << (Serializer& out, PersonInfo d) {
		out << d.age << d.name << d.height;
		return out;
	}
};

int main()
{
	buttonrpc client;
	client.as_client("127.0.0.1", 5555);
	client.set_timeout(2000);

	int callcnt = 0;
	while (1){
		std::cout << "current call count: " << ++callcnt << std::endl;

		client.call<void>("foo_1");

		client.call<void>("foo_2", 10);

		int foo3r = client.call<int>("foo_3", 10).val();
		buttont_assert(foo3r == 100);

		int foo4r = client.call<int>("foo_4", 10, "buttonrpc", 100, (float)10.8).val();
		buttont_assert(foo4r == 1000);

		PersonInfo  dd = { 10, "buttonrpc", 170 };
		dd = client.call<PersonInfo>("foo_5", dd, 120).val();
		buttont_assert(dd.age == 20);
		buttont_assert(dd.name == "buttonrpc is good");
		buttont_assert(dd.height == 180);

		int foo6r = client.call<int>("foo_6", 10, "buttonrpc", 100).val();
		buttont_assert(foo6r == 1000);

		buttonrpc::value_t<void> xx = client.call<void>("foo_7", 666);
		buttont_assert(!xx.valid());
#ifdef _WIN32
		Sleep(1000);
#else
        sleep(1);
#endif
	}

	return 0;
}

server代码

#include <string>
#include <iostream>
#include "buttonrpc.hpp"


#define buttont_assert(exp) { \
	if (!(exp)) {\
		std::cout << "ERROR: "; \
		std::cout << "function: " << __FUNCTION__  << ", line: " <<  __LINE__ << std::endl; \
		system("pause"); \
	}\
}\


// 测试例子
void foo_1() {
	std::cout << "foo_1()" << std::endl;
}

void foo_2(int arg1) {
	buttont_assert(arg1 == 10);
	std::cout << "foo_2(int arg1)" << std::endl;
}

int foo_3(int arg1) {
	buttont_assert(arg1 == 10);
	std::cout << "foo_3(int arg1)" << std::endl;
	return arg1 * arg1;
}

int foo_4(int arg1, std::string arg2, int arg3, float arg4) {
	buttont_assert(arg1 == 10);
	buttont_assert(arg2 == "buttonrpc");
	buttont_assert(arg3 == 100);
	buttont_assert((arg4 > 10.0) && (arg4 < 11.0));

	std::cout << "foo_4(int arg1, std::string arg2, int arg3, float arg4)" << std::endl;
	return arg1 * arg3;
}

class ClassMem
{
public:
	int bar(int arg1, std::string arg2, int arg3) {
		buttont_assert(arg1 == 10);
		buttont_assert(arg2 == "buttonrpc");
		buttont_assert(arg3 == 100);

		std::cout << "bar(int arg1, std::string arg2, int arg3)" << std::endl;

		return arg1 * arg3;
	}
};

struct PersonInfo
{
	int age;
	std::string name;
	float height;

	// must implement
	friend Serializer& operator >> (Serializer& in, PersonInfo& d) {
		in >> d.age >> d.name >> d.height;
		return in;
	}
	friend Serializer& operator << (Serializer& out, PersonInfo d) {
		out << d.age << d.name << d.height;
		return out;
	}
};

PersonInfo foo_5(PersonInfo d,  int weigth)
{
	buttont_assert(d.age == 10);
	buttont_assert(d.name == "buttonrpc");
	buttont_assert(d.height == 170);

	PersonInfo ret;
	ret.age = d.age + 10;
	ret.name = d.name + " is good";
	ret.height = d.height + 10;

	std::cout << "foo_5(PersonInfo d,  int weigth)" << std::endl;

	return ret;
}

int main()
{
	buttonrpc server;
	server.as_server(5555);

	server.bind("foo_1", foo_1);
	server.bind("foo_2", foo_2);
	server.bind("foo_3", std::function<int(int)>(foo_3));
	server.bind("foo_4", foo_4);
	server.bind("foo_5", foo_5);

	ClassMem s;
	server.bind("foo_6", &ClassMem::bar, &s);

	std::cout << "run rpc server on: " << 5555 << std::endl;
	server.run();

	return 0;
}

下载源码后,适用vs2015编译就可以使用了。

 

====================================================================================================================================================================================================================================================================================================================================================================

【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【【

1,操作符的重载

2,对象类型的隐式转换

】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】

c++ operator操作符的两种用法:重载和隐式类型转换,string转其他基本数据类型的简洁实现string_cast

C++中的operator主要有两个作用,一是操作符的重载,一是自定义对象类型的隐式转换。对于操作符的重载,许多人都不陌生,但是估计不少人都不太熟悉operator的第二种用法,即自定义对象类型的隐式转换,我们下面就用以下这个小例子温故一下这两种用法:

复制代码

 1 #include <iostream>
 2 #include <sstream>                                                                                                                                                     
 3 using namespace std;
 4 
 5 class FuncObj
 6 {
 7 public:
 8   FuncObj(int n): _n(n) {
 9     cout << "constructor" << endl;
10   }
11   
12   bool operator()(int v) {
13     cout << "operator overload" << endl;
14     return v > _n; 
15   }
16 
17   operator string() {
18     cout << "type convert" << endl;
19     stringstream sstr;
20     sstr << _n; 
21     return sstr.str();
22   }
23 
24   int _n; 
25 };
26 
27 int main()
28 {
29   FuncObj obj(10);
30   if (obj(11))
31     cout << "11 greater than 10" << endl;
32 
33   string str(obj);
34   cout << str << endl;
35 }  

复制代码

第12行是操作符重载,重载()使得该对象成为一个函数对象,即该对象有类似函数的功能,在很多场合下可以当成函数指针使用,在STL的很多算法模板里广泛使用。FuncObj用过操作符重载可以判断传入的参数是否大于一个预先设定好的值(在构造函数里指定),见代码的29~31行。

17行的定义表名FuncObj对象可以隐身转换成string,这就是operator的第二个用法,使用方法见代码的33~34行。注意在函数声明时,operator关键词出现在返回类型的前面,区别与操作符重载时的用法。

上述代码的输出:

constructor
operator overload
11 greater than 10
type convert
10

顺便说点题外话,第33行把FuncObj类型的对象传入string的构造函数,是用了c++构造函数的隐式类型转换特性,虽然string类并没有显式定义参数为FuncObj的构造函数,但因为其可以隐式转换为string,所以语法上都是合法的。构造函数的隐式类型转换,是使用一个其他的类型构造当前类的临时对象并用此临时对象来构造当前对象,这种转换必须有构造函数的支持;operator算子的隐式类型转换,使用当前对象去生成另一个类型的对象(正好与构造函数隐式转换相反),这种转换必须有operator算子的支持。当然了,构造函数的隐式类型转换有利有弊,类的设计者就起决定性作用了,如果你不想让构造函数发生隐式的类型转换,请在构造函数前加explicit关键字;同时,operator算子声明的隐式类型转换也可以通过一些相应的返回值函数替代,用户的掌控性更好。

最后,用过实现一个经常发生的普遍需求(string转其他基本数据类型)让读者加深一下,operator自定义对象类型的隐式转换功能的用法。

复制代码

 1   template <typename T>
 2   class string_cast
 3   {
 4   public:
 5     string_cast(const std::string &from): m_from(from) {
 6     }
 7     operator T() const {
 8       std::stringstream sstr(m_from);
 9       T ret;
10       try {
11         sstr >> ret;
12       }
13       catch(std::exception &e)
14       {
15          return T(0);
16       }
17       return ret;
18     }
19   private:
20     const std::string &m_from;
21   };

复制代码

string转int的用法:

cout << string_cast<int>("12345") << endl;

string转double的用法:

cout << string_cast<double>("12345.78") << endl;

是不是和c++的其他类型转换(static_cast,const_cast,dynamic_cast, reinterpret_cast)语法很相似?

 

标签: C++, operator, 操作符重载, 隐式类型转换, string_cast

 

===========================================================================================================================================================================================================================================================================

c++ operator操作符的两种用法:重载和隐式类型转换,string转其他基本数据类型的简洁实现string_cast

C++中的operator主要有两个作用,一是操作符的重载,一是自定义对象类型的隐式转换。对于操作符的重载,许多人都不陌生,但是估计不少人都不太熟悉operator的第二种用法,即自定义对象类型的隐式转换,我们下面就用以下这个小例子温故一下这两种用法:

复制代码

 1 #include <iostream>
 2 #include <sstream>                                                                                                                                                     
 3 using namespace std;
 4 
 5 class FuncObj
 6 {
 7 public:
 8   FuncObj(int n): _n(n) {
 9     cout << "constructor" << endl;
10   }
11   
12   bool operator()(int v) {
13     cout << "operator overload" << endl;
14     return v > _n; 
15   }
16 
17   operator string() {
18     cout << "type convert" << endl;
19     stringstream sstr;
20     sstr << _n; 
21     return sstr.str();
22   }
23 
24   int _n; 
25 };
26 
27 int main()
28 {
29   FuncObj obj(10);
30   if (obj(11))
31     cout << "11 greater than 10" << endl;
32 
33   string str(obj);
34   cout << str << endl;
35 }  

复制代码

第12行是操作符重载,重载()使得该对象成为一个函数对象,即该对象有类似函数的功能,在很多场合下可以当成函数指针使用,在STL的很多算法模板里广泛使用。FuncObj用过操作符重载可以判断传入的参数是否大于一个预先设定好的值(在构造函数里指定),见代码的29~31行。

17行的定义表名FuncObj对象可以隐身转换成string,这就是operator的第二个用法,使用方法见代码的33~34行。注意在函数声明时,operator关键词出现在返回类型的前面,区别与操作符重载时的用法。

上述代码的输出:

constructor
operator overload
11 greater than 10
type convert
10

顺便说点题外话,第33行把FuncObj类型的对象传入string的构造函数,是用了c++构造函数的隐式类型转换特性,虽然string类并没有显式定义参数为FuncObj的构造函数,但因为其可以隐式转换为string,所以语法上都是合法的。构造函数的隐式类型转换,是使用一个其他的类型构造当前类的临时对象并用此临时对象来构造当前对象,这种转换必须有构造函数的支持;operator算子的隐式类型转换,使用当前对象去生成另一个类型的对象(正好与构造函数隐式转换相反),这种转换必须有operator算子的支持。当然了,构造函数的隐式类型转换有利有弊,类的设计者就起决定性作用了,如果你不想让构造函数发生隐式的类型转换,请在构造函数前加explicit关键字;同时,operator算子声明的隐式类型转换也可以通过一些相应的返回值函数替代,用户的掌控性更好。

最后,用过实现一个经常发生的普遍需求(string转其他基本数据类型)让读者加深一下,operator自定义对象类型的隐式转换功能的用法。

复制代码

 1   template <typename T>
 2   class string_cast
 3   {
 4   public:
 5     string_cast(const std::string &from): m_from(from) {
 6     }
 7     operator T() const {
 8       std::stringstream sstr(m_from);
 9       T ret;
10       try {
11         sstr >> ret;
12       }
13       catch(std::exception &e)
14       {
15          return T(0);
16       }
17       return ret;
18     }
19   private:
20     const std::string &m_from;
21   };

复制代码

string转int的用法:

cout << string_cast<int>("12345") << endl;

string转double的用法:

cout << string_cast<double>("12345.78") << endl;

是不是和c++的其他类型转换(static_cast,const_cast,dynamic_cast, reinterpret_cast)语法很相似?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值