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:注冊服務函數
void test5(string a, int b, map<int, map<int, string>> vlist)
{
}
rpc rpc_server; /*rpc服務器*/
rpc_server.def("test5", test5);
2:客戶端調用遠程函數
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
rpc_server.handleRpc(rpc_request_msg);
其中 rpc_request_msg為接受到的網絡消息(字符串)。
這樣就會自動調用到我們的 test5 函數。 並且形參已經(自動)准備OK。你只需要在test5 里使用這些參數即可。(不用關心網絡消息協議)。
4:被調用方可以返回數據給調用方
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, 前述所有類型/遞歸> )
整個測試代碼:
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:
我們來看看最新戰果:
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,再結合網絡庫,就能很輕松的開發業務邏輯。
