FFRPC應(yīng)用 Client/Server使用及原理解析
摘要:
Ffrpc 進(jìn)行了重構(gòu),精簡(jiǎn)了代碼,代碼更加清晰簡(jiǎn)潔,幾乎完美的達(dá)到了我的預(yù)想。接下來(lái)將寫(xiě)幾遍文章來(lái)介紹ffrpc可以做什么。簡(jiǎn)單總結(jié)ffrpc的特性是:
- Ffrpc是c++ 網(wǎng)絡(luò)通信庫(kù)
- 全異步 + 回調(diào)函數(shù) 機(jī)制
- 支持普通二進(jìn)制協(xié)議、protobuf、thrift
- 基于Broker模式設(shè)計(jì)
- 設(shè)計(jì)精巧,代碼量小,核心ffrpc的代碼只有1000行
- 接口的性能監(jiān)控是集成式的,使用者自動(dòng)獲得了接口性能數(shù)據(jù),方便優(yōu)化接口
普通二進(jìn)制協(xié)議示例
Ffrpc實(shí)現(xiàn)了一個(gè)最基本的二進(jìn)制序列化方法,基本的原理就是如果是固定長(zhǎng)度那么就直接拷貝,如果是字符串,就先拷貝長(zhǎng)度再拷貝內(nèi)容。所以只支持向后擴(kuò)展字段,對(duì)其他語(yǔ)言支持也不方便,但如果只是c++語(yǔ)言間傳遞消息,則顯得非常的方便和高效。比如網(wǎng)游服務(wù)器中各個(gè)進(jìn)程的通信可以采用這種最簡(jiǎn)單的二進(jìn)制協(xié)議。Ffrpc中定義了一個(gè)工具類ffmsg_t來(lái)定義二進(jìn)制消息.
消息定義:
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中提供了流式的序列化方法,使得序列化變得很容易。設(shè)計(jì)服務(wù)器消息的時(shí)候,需要注意的點(diǎn)有:
- 在設(shè)計(jì)服務(wù)器接口的時(shí)候,每個(gè)接口接受一個(gè)消息作為參數(shù),一個(gè)處理完畢返回一個(gè)消息,這是最傳統(tǒng)的rpc模式。Ffrpc中采用這樣的設(shè)計(jì)理念以簡(jiǎn)化和規(guī)范化接口設(shè)計(jì)。如果使用ffmsg_t定義消息,本人推薦的定義風(fēng)格類似上面的代碼這樣。上面定義的是echo接口的輸入消息和輸出消息,但是都定義在echo_t結(jié)構(gòu)內(nèi)可以清晰的表明這是一對(duì)接口消息。
- 傳統(tǒng)的服務(wù)器接口會(huì)為每個(gè)接口定義一個(gè)cmd,然后通過(guò)cmd反序列化成特定的消息調(diào)用特定的接口,ffrpc省略了cmd的定義,而是直接采用消息名稱作為cmd,比如在ffrpc中注冊(cè)的接口接受echo_t的消息,那么收到echo_t的消息自然而言的是調(diào)用這個(gè)接口
- 接口定義的時(shí)候必須的同時(shí)制定輸入消息和輸出消息
- Ffmsg_t支持普通類型,字符串類型、stl類型。
echo服務(wù)的實(shí)現(xiàn)代碼:
struct echo_service_t
{
//! echo接口,返回請(qǐng)求的發(fā)送的消息ffreq_t可以提供兩個(gè)模板參數(shù),第一個(gè)表示輸入的消息(請(qǐng)求者發(fā)送的)
//! 第二個(gè)模板參數(shù)表示該接口要返回的結(jié)果消息類型
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客戶端,可以注冊(cè)到broker,并注冊(cè)服務(wù)以及接口,也可以遠(yuǎn)程調(diào)用其他節(jié)點(diǎn)的接口
ffrpc_t ffrpc_service("echo");
ffrpc_service.reg(&echo_service_t::echo, &foo);
if (ffrpc_service.open(arg_helper))
{
return -1;
}
這樣就定義了echo服務(wù),echo服務(wù)提供了一個(gè)接口,接受echo_t::in_t消息,返回echo_t::out_t消息。由此可見(jiàn)使用ffrpc定義服務(wù)的步驟是:
l 定義消息和接口
將接口注冊(cè)到ffrpc的示例中,ffpc提供了reg模板方法,會(huì)自動(dòng)的分析注冊(cè)的接口使用神馬輸入消息,從而保證如果echo_t::in_t消息到來(lái)一定會(huì)調(diào)用對(duì)應(yīng)的接口
Ffrpc工作的核心是broker,簡(jiǎn)單描述broker的作用就是轉(zhuǎn)發(fā)消息。Ffrpc的client和server是不直接連接的,而是通過(guò)broker轉(zhuǎn)發(fā)消息進(jìn)行通信。
這樣的好處是server的位置對(duì)于client是完全透明的,這也是broker模式最精髓的思想。所以ffrpc天生就是scalability的。Ffrpc的client比如要調(diào)用echo服務(wù)的接口,完全不需要知道serverr對(duì)應(yīng)的位置或者配置,只需要知道echo服務(wù)的名字。有人可能擔(dān)憂完全的broker轉(zhuǎn)發(fā)可能會(huì)帶來(lái)很大開(kāi)銷。
Broker保證了消息轉(zhuǎn)發(fā)的最佳優(yōu)化,如果client或者server和broker在同一進(jìn)程,那么消息直接是內(nèi)存間傳遞的,連序列化都不需要做,這也是得益于broker模式,broker模式的特點(diǎn)就是擁有很好的scalability。這樣無(wú)論是簡(jiǎn)單的設(shè)計(jì)一個(gè)單進(jìn)程的server還是設(shè)計(jì)成多進(jìn)程分布式的一組服務(wù),ffrpc都能完美勝任。
調(diào)用echo服務(wù)的client示例:
struct echo_client_t
{
//! 遠(yuǎn)程調(diào)用接口,可以指定回調(diào)函數(shù)(也可以留空),同樣使用ffreq_t指定輸入消息類型,并且可以使用lambda綁定參數(shù)
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調(diào)用遠(yuǎn)程接口,只需要制定服務(wù)名和輸入消息,broker自動(dòng)定位echo服務(wù)的位置,本示例中由于ffrpc的client和server在同一進(jìn)程,那么自動(dòng)通過(guò)內(nèi)存間傳遞,如果server和broker在同一進(jìn)程,而client在其他進(jìn)程或者物理機(jī)上,則broker和server之間的傳遞為內(nèi)存?zhèn)鬟f,broker和client的消息傳遞為tcp傳輸,這就跟自己寫(xiě)一個(gè)tcp的server收到消息投遞給service的接口,然后將消息再通過(guò)tcp投遞給client。但是必須看到,ffrpc完全簡(jiǎn)化了tcp server定義,并且更加scalability,甚至完全可以用來(lái)進(jìn)程內(nèi)多線程的通訊。
需要注意的是,ffrpc擁有良好的容錯(cuò)能力,如果服務(wù)不存在或者接口不存在或者異常等發(fā)生回調(diào)函數(shù)仍然是會(huì)被調(diào)用,并且返回錯(cuò)誤信息,從而使錯(cuò)誤處理變得更加容易。比如游戲服務(wù)器中client登入gate但是scene可能還沒(méi)有啟動(dòng)的時(shí)候,這里就能夠很好的處理,回調(diào)函數(shù)檢查錯(cuò)誤就可以了。對(duì)于回調(diào)函數(shù),對(duì)于經(jīng)常使用多線程和任務(wù)隊(duì)列的開(kāi)發(fā)者一定非常熟悉,回調(diào)函數(shù)支持lambda參數(shù)應(yīng)該算是錦上添花,使得異步的代碼變得更加清晰易懂。
Broker的啟動(dòng)方式:
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);
//! 啟動(dòng)broker,負(fù)責(zé)網(wǎng)絡(luò)相關(guān)的操作,如消息轉(zhuǎn)發(fā),節(jié)點(diǎn)注冊(cè),重連等
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中兩個(gè)關(guān)鍵的組件broker和rpc,broker負(fù)責(zé)轉(zhuǎn)發(fā)和注冊(cè)服務(wù)器,rpc則代表通信節(jié)點(diǎn),可能是client可能是server。即使是多個(gè)服務(wù)器,只需要broker一個(gè)監(jiān)聽(tīng)的端口,其他的服務(wù)只需要提供不同的服務(wù)名即可。
Protobuf協(xié)議示例
Ffrpc 良好的設(shè)計(jì)抽離了對(duì)于協(xié)議的耦合,使得支持protobuf就增加了10來(lái)行代碼。當(dāng)然這也是由于protobuf生成的消息都繼承message基類。當(dāng)我實(shí)現(xiàn)thrift的時(shí)候,事情就稍微麻煩一些,thrift生成的代碼更加簡(jiǎn)潔,但是生成的消息不集成基類,需要復(fù)制粘貼一些代碼。
Protobuf的定義文件:
package ff;
message pb_echo_in_t {
required string data = 1;
}
message pb_echo_out_t {
required string data = 1;
}
我們?nèi)匀辉O(shè)計(jì)一個(gè)echo服務(wù),定義echo接口的消息,基于ffrpc的設(shè)計(jì)理念,每個(gè)接口都有一個(gè)輸入消息和輸出消息。
Echo服務(wù)的實(shí)現(xiàn)代碼:
struct protobuf_service_t
{
//! echo接口,返回請(qǐng)求的發(fā)送的消息ffreq_t可以提供兩個(gè)模板參數(shù),第一個(gè)表示輸入的消息(請(qǐng)求者發(fā)送的)
//! 第二個(gè)模板參數(shù)表示該接口要返回的結(jié)果消息類型
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客戶端,可以注冊(cè)到broker,并注冊(cè)服務(wù)以及接口,也可以遠(yuǎn)程調(diào)用其他節(jié)點(diǎn)的接口
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字段是輸入的消息。
調(diào)用echo服務(wù)器的client的示例代碼
struct protobuf_client_t
{
//! 遠(yuǎn)程調(diào)用接口,可以指定回調(diào)函數(shù)(也可以留空),同樣使用ffreq_t指定輸入消息類型,并且可以使用lambda綁定參數(shù)
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的優(yōu)點(diǎn)是:
支持版本,這樣增加字段變得更加容易
Protobuf是支持多語(yǔ)言的,這樣可以跟其他的語(yǔ)言也可以通訊
Thrift協(xié)議的示例
Thrift 定義文件:
namespace cpp ff
struct echo_thrift_in_t {
1: string data
}
struct echo_thrift_out_t {
1: string data
}
Thrift 的服務(wù)器實(shí)現(xiàn)代碼:
struct thrift_service_t
{
//! echo接口,返回請(qǐng)求的發(fā)送的消息ffreq_t可以提供兩個(gè)模板參數(shù),第一個(gè)表示輸入的消息(請(qǐng)求者發(fā)送的)
//! 第二個(gè)模板參數(shù)表示該接口要返回的結(jié)果消息類型
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客戶端,可以注冊(cè)到broker,并注冊(cè)服務(wù)以及接口,也可以遠(yuǎn)程調(diào)用其他節(jié)點(diǎn)的接口
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;
}
調(diào)用 echo的client的示例:
struct thrift_client_t
{
//! 遠(yuǎn)程調(diào)用接口,可以指定回調(diào)函數(shù)(也可以留空),同樣使用ffreq_t指定輸入消息類型,并且可以使用lambda綁定參數(shù)
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的優(yōu)缺點(diǎn):
- Thrift 更加靈活,支持list和map,而且可以嵌套
- 支持N種語(yǔ)言
- 官方的版本需要依賴boost,ffrpc從中提取出一個(gè)最基本的c++版本,只有頭文件,不依賴boost
總結(jié)
- Ffrpc是基于c++的網(wǎng)絡(luò)通訊庫(kù),基于broker模式scalability和 易用性是最大的優(yōu)點(diǎn)
- 使用ffrpc進(jìn)行進(jìn)程間通訊非常的容易,定義服務(wù)和接口就行了,你除了使用ffmsg_t最傳統(tǒng)的消息定義,也可以使用google protobuf和facebook thrift。
- Ffrpc是全異步的,通過(guò)回調(diào)函數(shù)+lambda方式可以很容易操作異步邏輯。
- Ffrpc 接下來(lái)會(huì)有更多的示例,當(dāng)系統(tǒng)復(fù)雜時(shí),ffrpc的優(yōu)勢(shì)將會(huì)更加明顯。
- Github的地址: https://github.com/fanchy/FFRPC
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:華為筆試算法題匯總
欄 目:C語(yǔ)言
下一篇:C++結(jié)構(gòu)體與類指針知識(shí)點(diǎn)總結(jié)
本文標(biāo)題:FFRPC應(yīng)用 Client/Server使用及原理解析
本文地址:http://www.jygsgssxh.com/a1/Cyuyan/217.html
您可能感興趣的文章
- 01-10HDOJ 1443 約瑟夫環(huán)的最新應(yīng)用分析詳解
- 01-10解析sizeof, strlen, 指針以及數(shù)組作為函數(shù)參數(shù)的應(yīng)用
- 01-10深入探討C++父類子類中虛函數(shù)的應(yīng)用
- 01-10深入理解卡特蘭數(shù)及其應(yīng)用
- 01-10Reactor反應(yīng)器的實(shí)現(xiàn)方法詳解
- 01-10C++ clock()解析如何使用時(shí)鐘計(jì)時(shí)的應(yīng)用
- 01-10如何將C語(yǔ)言代碼轉(zhuǎn)換為應(yīng)用程序(也就是編譯)
- 01-10淺析C++中前置聲明的應(yīng)用與陷阱
- 01-10共用體的定義與應(yīng)用詳細(xì)解析
- 01-10C++中引用(&amp;)的用法與應(yīng)用實(shí)例分析


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 01-10delphi制作wav文件的方法
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 04-02jquery與jsp,用jquery
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改


