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,再結合網絡庫,就能很輕松的開發業務邏輯。