您好,登錄后才能下訂單哦!
摘要:
Ffrpc 進行了重構,精簡了代碼,代碼更加清晰簡潔,幾乎完美的達到了我的預想。接下來將寫幾遍文章來介紹ffrpc可以做什么。簡單總結ffrpc的特性是:
普通二進制協議示例
Ffrpc實現了一個最基本的二進制序列化方法,基本的原理就是如果是固定長度那么就直接拷貝,如果是字符串,就先拷貝長度再拷貝內容。所以只支持向后擴展字段,對其他語言支持也不方便,但如果只是c++語言間傳遞消息,則顯得非常的方便和高效。比如網游服務器中各個進程的通信可以采用這種最簡單的二進制協議。Ffrpc中定義了一個工具類ffmsg_t來定義二進制消息.
消息定義:
struct echo_t { struct in_t: public ffmsg_t<in_t> { void encode() { encoder() << data; } void decode() { decoder() >> data; } string data; }; struct out_t: public ffmsg_t<out_t> { void encode() { encoder() << data; } void decode() { decoder() >> data; } string data; }; };
讀者可以看到,ffmsg_t中提供了流式的序列化方法,使得序列化變得很容易。設計服務器消息的時候,需要注意的點有:
echo服務的實現代碼:
struct echo_service_t { //! echo接口,返回請求的發送的消息ffreq_t可以提供兩個模板參數,第一個表示輸入的消息(請求者發送的) //! 第二個模板參數表示該接口要返回的結果消息類型 void echo(ffreq_t<echo_t::in_t, echo_t::out_t>& req_) { echo_t::out_t out; out.data = req_.msg.data; LOGINFO(("XX", "foo_t::echo: recv %s", req_.msg.data.c_str())); req_.response(out); } }; echo_service_t foo; //! broker客戶端,可以注冊到broker,并注冊服務以及接口,也可以遠程調用其他節點的接口 ffrpc_t ffrpc_service("echo"); ffrpc_service.reg(&echo_service_t::echo, &foo); if (ffrpc_service.open(arg_helper)) { return -1; }
這樣就定義了echo服務,echo服務提供了一個接口,接受echo_t::in_t消息,返回echo_t::out_t消息。由此可見使用ffrpc定義服務的步驟是:
l 定義消息和接口
將接口注冊到ffrpc的示例中,ffpc提供了reg模板方法,會自動的分析注冊的接口使用神馬輸入消息,從而保證如果echo_t::in_t消息到來一定會調用對應的接口
Ffrpc工作的核心是broker,簡單描述broker的作用就是轉發消息。Ffrpc的client和server是不直接連接的,而是通過broker轉發消息進行通信。
這樣的好處是server的位置對于client是完全透明的,這也是broker模式最精髓的思想。所以ffrpc天生就是scalability的。Ffrpc的client比如要調用echo服務的接口,完全不需要知道serverr對應的位置或者配置,只需要知道echo服務的名字。有人可能擔憂完全的broker轉發可能會帶來很大開銷。
Broker保證了消息轉發的最佳優化,如果client或者server和broker在同一進程,那么消息直接是內存間傳遞的,連序列化都不需要做,這也是得益于broker模式,broker模式的特點就是擁有很好的scalability。這樣無論是簡單的設計一個單進程的server還是設計成多進程分布式的一組服務,ffrpc都能完美勝任。
調用echo服務的client示例:
struct echo_client_t { //! 遠程調用接口,可以指定回調函數(也可以留空),同樣使用ffreq_t指定輸入消息類型,并且可以使用lambda綁定參數 void echo_callback(ffreq_t<echo_t::out_t>& req_, int index, ffrpc_t* ffrpc_client) { if (req_.error()) { LOGERROR(("XX", "error_msg <%s>", req_.error_msg())); return; } else if (index < 10) { echo_t::in_t in; in.data = "helloworld"; LOGINFO(("XX", "%s %s index=%d callback...", __FUNCTION__, req_.msg.data.c_str(), index)); sleep(1); ffrpc_client->call("echo", in, ffrpc_ops_t::gen_callback(&echo_client_t::echo_callback, this, ++index, ffrpc_client)); } else { LOGINFO(("XX", "%s %s %d callback end", __FUNCTION__, req_.msg.data.c_str(), index)); } } }; ffrpc_t ffrpc_client; if (ffrpc_client.open(arg_helper)) { return -1; } echo_t::in_t in; in.data = "helloworld"; echo_client_t client; ffrpc_client.call("echo", in, ffrpc_ops_t::gen_callback(&echo_client_t::echo_callback, &client, 1, &ffrpc_client));
使用ffrpc調用遠程接口,只需要制定服務名和輸入消息,broker自動定位echo服務的位置,本示例中由于ffrpc的client和server在同一進程,那么自動通過內存間傳遞,如果server和broker在同一進程,而client在其他進程或者物理機上,則broker和server之間的傳遞為內存傳遞,broker和client的消息傳遞為tcp傳輸,這就跟自己寫一個tcp的server收到消息投遞給service的接口,然后將消息再通過tcp投遞給client。但是必須看到,ffrpc完全簡化了tcp server定義,并且更加scalability,甚至完全可以用來進程內多線程的通訊。
需要注意的是,ffrpc擁有良好的容錯能力,如果服務不存在或者接口不存在或者異常等發生回調函數仍然是會被調用,并且返回錯誤信息,從而使錯誤處理變得更加容易。比如游戲服務器中client登入gate但是scene可能還沒有啟動的時候,這里就能夠很好的處理,回調函數檢查錯誤就可以了。對于回調函數,對于經常使用多線程和任務隊列的開發者一定非常熟悉,回調函數支持lambda參數應該算是錦上添花,使得異步的代碼變得更加清晰易懂。
Broker的啟動方式:
int main(int argc, char* argv[]) { //! 美麗的日志組件,shell輸出是彩色滴!! LOG.start("-log_path ./log -log_filename log -log_class XX,BROKER,FFRPC -log_print_screen true -log_print_file false -log_level 3"); if (argc == 1) { printf("usage: %s -broker tcp://127.0.0.1:10241\n", argv[0]); return 1; } arg_helper_t arg_helper(argc, argv); //! 啟動broker,負責網絡相關的操作,如消息轉發,節點注冊,重連等 ffbroker_t ffbroker; if (ffbroker.open(arg_helper)) { return -1; } sleep(1); if (arg_helper.is_enable_option("-echo_test")) { run_echo_test(arg_helper); } else if (arg_helper.is_enable_option("-protobuf_test")) { run_protobuf_test(arg_helper); } else { printf("usage %s -broker tcp://127.0.0.1:10241 -echo_test\n", argv[0]); return -1; } ffbroker.close(); return 0; }
Ffrpc中兩個關鍵的組件broker和rpc,broker負責轉發和注冊服務器,rpc則代表通信節點,可能是client可能是server。即使是多個服務器,只需要broker一個監聽的端口,其他的服務只需要提供不同的服務名即可。
Protobuf協議示例
Ffrpc 良好的設計抽離了對于協議的耦合,使得支持protobuf就增加了10來行代碼。當然這也是由于protobuf生成的消息都繼承message基類。當我實現thrift的時候,事情就稍微麻煩一些,thrift生成的代碼更加簡潔,但是生成的消息不集成基類,需要復制粘貼一些代碼。
Protobuf的定義文件:
package ff; message pb_echo_in_t { required string data = 1; } message pb_echo_out_t { required string data = 1; }
我們仍然設計一個echo服務,定義echo接口的消息,基于ffrpc的設計理念,每個接口都有一個輸入消息和輸出消息。
Echo服務的實現代碼:
struct protobuf_service_t { //! echo接口,返回請求的發送的消息ffreq_t可以提供兩個模板參數,第一個表示輸入的消息(請求者發送的) //! 第二個模板參數表示該接口要返回的結果消息類型 void echo(ffreq_t<pb_echo_in_t, pb_echo_out_t>& req_) { LOGINFO(("XX", "foo_t::echo: recv data=%s", req_.msg.data())); pb_echo_out_t out; out.set_data("123456"); req_.response(out); } }; protobuf_service_t foo; //! broker客戶端,可以注冊到broker,并注冊服務以及接口,也可以遠程調用其他節點的接口 ffrpc_t ffrpc_service("echo"); ffrpc_service.reg(&protobuf_service_t::echo, &foo); if (ffrpc_service.open(arg_helper)) { return -1; }
跟使用ffmsg_t的方式幾乎是一樣的,ffreq_t 的msg字段是輸入的消息。
調用echo服務器的client的示例代碼
struct protobuf_client_t { //! 遠程調用接口,可以指定回調函數(也可以留空),同樣使用ffreq_t指定輸入消息類型,并且可以使用lambda綁定參數 void echo_callback(ffreq_t<pb_echo_out_t>& req_, int index, ffrpc_t* ffrpc_client) { if (req_.error()) { LOGERROR(("XX", "error_msg <%s>", req_.error_msg())); return; } else if (index < 10) { pb_echo_in_t in; in.set_data("Ohnice"); LOGINFO(("XX", "%s data=%s index=%d callback...", __FUNCTION__, req_.msg.data(), index)); sleep(1); ffrpc_client->call("echo", in, ffrpc_ops_t::gen_callback(&protobuf_client_t::echo_callback, this, ++index, ffrpc_client)); } else { LOGINFO(("XX", "%s %d callback end", __FUNCTION__, index)); } } }; ffrpc_t ffrpc_client; if (ffrpc_client.open(arg_helper)) { return -1; } protobuf_client_t client; pb_echo_in_t in; in.set_data("Ohnice"); ffrpc_client.call("echo", in, ffrpc_ops_t::gen_callback(&protobuf_client_t::echo_callback, &client, 1, &ffrpc_client));
Protobuf的優點是:
支持版本,這樣增加字段變得更加容易
Protobuf是支持多語言的,這樣可以跟其他的語言也可以通訊
Thrift協議的示例
Thrift 定義文件:
namespace cpp ff struct echo_thrift_in_t { 1: string data } struct echo_thrift_out_t { 1: string data }
Thrift 的服務器實現代碼:
struct thrift_service_t { //! echo接口,返回請求的發送的消息ffreq_t可以提供兩個模板參數,第一個表示輸入的消息(請求者發送的) //! 第二個模板參數表示該接口要返回的結果消息類型 void echo(ffreq_thrift_t<echo_thrift_in_t, echo_thrift_out_t>& req_) { LOGINFO(("XX", "foo_t::echo: recv data=%s", req_.msg.data)); echo_thrift_out_t out; out.data = "123456"; req_.response(out); } }; thrift_service_t foo; //! broker客戶端,可以注冊到broker,并注冊服務以及接口,也可以遠程調用其他節點的接口 ffrpc_t ffrpc_service("echo"); ffrpc_service.reg(&thrift_service_t::echo, &foo); if (ffrpc_service.open(arg_helper)) { return -1; } ffrpc_t ffrpc_client; if (ffrpc_client.open(arg_helper)) { return -1; }
調用 echo的client的示例:
struct thrift_client_t { //! 遠程調用接口,可以指定回調函數(也可以留空),同樣使用ffreq_t指定輸入消息類型,并且可以使用lambda綁定參數 void echo_callback(ffreq_thrift_t<echo_thrift_out_t>& req_, int index, ffrpc_t* ffrpc_client) { if (req_.error()) { LOGERROR(("XX", "error_msg <%s>", req_.error_msg())); return; } else if (index < 10) { echo_thrift_in_t in; in.data = "Ohnice"; LOGINFO(("XX", "%s data=%s index=%d callback...", __FUNCTION__, req_.msg.data, index)); sleep(1); ffrpc_client->call("echo", in, ffrpc_ops_t::gen_callback(&thrift_client_t::echo_callback, this, ++index, ffrpc_client)); } else { LOGINFO(("XX", "%s %d callback end", __FUNCTION__, index)); } } }; ffrpc_t ffrpc_client; if (ffrpc_client.open(arg_helper)) { return -1; } thrift_client_t client; echo_thrift_in_t in; in.data = "Ohnice"; ffrpc_client.call("echo", in, ffrpc_ops_t::gen_callback(&thrift_client_t::echo_callback, &client, 1, &ffrpc_client));
Thrift的優缺點:
總結
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。