<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          剛學會 C++ 的小白用這個開源框架,做個 RPC 服務(wù)要多久?

          共 3237字,需瀏覽 7分鐘

           ·

          2021-02-22 08:51

          本文適合有 C++ 基礎(chǔ)的朋友

          本文作者:HelloGitHub-Anthony

          HelloGitHub 推出的《講解開源項目》系列,本期介紹基于 C++ 的 RPC 開源框架——rest_rpc,一個讓小白也可以快速(10 分鐘)開發(fā) RPC 服務(wù)的框架。

          項目地址:https://github.com/qicosmos/rest_rpc

          rest_rpc 是一個高性能、易用、跨平臺、header only 的 C++11 RPC 庫,它的目標是讓 TCP 通信變得非常簡單易用,即使不懂網(wǎng)絡(luò)通信的人也可以直接使用它、快速上手。同時使用者只需要關(guān)注自己的業(yè)務(wù)邏輯即可。

          簡而言之 rest_rpc 能讓您能在沒有任何網(wǎng)絡(luò)編程相關(guān)知識的情況下通過幾行代碼快速編寫屬于自己的網(wǎng)絡(luò)程序,而且使用非常方便,是入門網(wǎng)絡(luò)編程及 RPC 框架的不二之選!

          一、預(yù)備知識

          1.1 什么是 RPC

          RPC 是 Remote Procedure Call 即 遠程過程調(diào)用 的縮寫。

          1.2 RPC 有什么用

          舉個例子來講,有兩臺服務(wù)器 A、B 現(xiàn)在 A 上的程序想要遠程調(diào)用 B 上應(yīng)用提供的函數(shù)/方法,就需要通過網(wǎng)絡(luò)來傳輸調(diào)用所需的消息。

          但是消息的網(wǎng)絡(luò)傳輸涉及很多東西,例如:

          • 客戶端和服務(wù)端間 TCP 連接的建立、維持和斷開

          • 消息的序列化、編組

          • 消息的網(wǎng)絡(luò)傳輸

          • 消息的反序列化

          • 等等

          RPC 的作用就是屏蔽網(wǎng)絡(luò)相關(guān)操作,讓不在一個內(nèi)存空間,甚至不在一個機器內(nèi)的程序可以像調(diào)用普通函數(shù)一樣被調(diào)用。

          1.3 rest_rpc 優(yōu)點

          rest_rpc 有很多的優(yōu)點:

          • 使用簡單
          • 支持訂閱模式
          • 允許 futurecallback 兩種異步調(diào)用接口,滿足不同人群愛好

          二、快速開始

          rest_rpc 依賴 Boost 在使用之前應(yīng)正確安裝 Boost.

          2.1 安裝

          通過 git clone 命令將項目下載到本地:

          git?clone?https://github.com/qicosmos/rest_rpc

          2.2 目錄結(jié)構(gòu)

          rest_rpc 項目根目錄中文件及其意義如表所示:

          文件名作用
          docrest_rpc 性能測試報告
          examplesrest_rpc 例子,包含 client 和 server 兩部分
          includerest_rpc 框架頭文件
          thirdmsgpack 支持庫,用于用序列化和反序列化消息

          2.3 運行例程

          rest_rpc 例程為 visual studio 工程,客戶端和服務(wù)端例程分別存儲在 examples/clientexamples/server 中,直接使用 visual studio 打開 basic_client.vcxprojbasic_server.vcxproj 后直接編譯即可,官方例程運行效果如圖:

          注意:項目需要 Boost/asio 支持,如未安裝 Boost 需要先正確安裝 Boost后將 Boost 添加到工程。

          工程中添加 Boost ?方法如下:

          1. 打開工程后點擊菜單欄中的 項目屬性(快捷鍵 ?Alt+F7)
          2. 選擇左邊的 VC++ 目錄 選項,在右邊的 包含目錄庫目錄 中添加 Boost根目錄依賴庫 后保存

          我使用的為 Boost 1.75 安裝目錄為 D:\devPack\boost_1_75_0,配置過程如圖所示:

          三、詳細教程

          3.1 寫在前面

          無論 服務(wù)端 還是 客戶端 都只用包含 include/rest_rpc.hpp 這一個文件即可。

          所有示例代碼都是用了如下內(nèi)容作為框架

          #include?
          #include?
          #include?
          using?namespace?rest_rpc;
          using?namespace?rest_rpc::rpc_service;

          int?main(){
          ????//?do?something
          }

          3.2 編寫服務(wù)端

          生成一個能提供服務(wù)的客戶端要經(jīng)歷一下幾個過程:

          1. rpc_server 對象的實例化,設(shè)置監(jiān)聽端口等屬性
          2. 服務(wù)函數(shù)的注冊,定義服務(wù)端提供哪些服務(wù)
          3. 服務(wù)的啟動

          1)rpc_server

          rpc_serverrest_rpc 服務(wù)端對象,負責注冊服務(wù)、發(fā)布訂閱、線程池管理等服務(wù)端基本功能,位于 rest_rpc::rpc_service 命名空間。

          使用時需要先實例化一個 rpc_server 對象并提供 監(jiān)聽端口、線程池大小,例如:

          rpc_server?server(9000,?6);?//?監(jiān)聽?9000?端口,線程池大小為?6

          2)服務(wù)端注冊與啟動

          rpc_server 提供了 register_handler 方法注冊服務(wù)以及 run 方法啟動服務(wù)端,具體例子如下:

          /*服務(wù)函數(shù)第一個參數(shù)必須為?rpc_conn,然后才是實現(xiàn)功能需要的參數(shù)(為可變參數(shù),數(shù)量可變,也可以沒有*/
          std::string?hello(rpc_conn?conn,?std::string?name){?
          ?/*可以為?void?返回類型,代表調(diào)用后不給遠程客戶端返回消息*/
          ????return?("Hello?"?+?name);?/*返回給遠程客戶端的內(nèi)容*/
          }


          int?main(){
          ????rpc_server?server(9000,?6);
          ????
          ????/*func_greet?為服務(wù)名,遠程調(diào)用通過服務(wù)名確定調(diào)用函數(shù)*/
          ????/*hello?為函數(shù),綁定當前服務(wù)調(diào)用哪個函數(shù)*/
          ????server.register_handler("func_greet",?hello);
          ?
          ????server.run();//啟動服務(wù)端
          ????
          ????return?EXIT_SUCCESS;
          }

          其中 function 可以為 仿函數(shù)lambda,例子分別如下:

          使用仿函數(shù)

          /*仿函數(shù)方法*/

          struct?test_func{
          ????std::string?hello(rpc_conn?conn){
          ????????return?"Hello?Github!";
          ????}
          };


          int?main(){
          ????test_func?greeting;
          ????rpc_server?server(9000,?6);
          ????
          ????/*greet?為服務(wù)名,遠程調(diào)用通過服務(wù)名確定調(diào)用函數(shù)*/
          ????/*test_func::hello?為函數(shù),綁定當前服務(wù)調(diào)用哪個函數(shù)*/
          ????/*greeting?為實例化仿函數(shù)對象*/
          ????server.register_handler("greet",?&test_func::hello,?&greeting);
          ????
          ????server.run();//啟動服務(wù)端
          ????
          ????return?EXIT_SUCCESS;
          }

          使用 lambda 方法的例子

          /*使用?lambda?方法*/

          int?main(){
          ????rpc_server?server(9000,?6);
          ????
          ????/*call_lambda?為服務(wù)名,遠程調(diào)用通過服務(wù)名確定調(diào)用函數(shù)*/
          ????/*[&server](rpc_conn?conn){...}?為?lambda?對象*/
          ????server.register_handler("call_lambda",?
          ????????????????????????????/*除?conn?外其他參數(shù)為可變參數(shù)*/
          ????????????????????????????[&server](rpc_conn?conn?/*其他參數(shù)可有可無*/)?{
          ????????????????????????????????std::cout?<"Hello?Github!"?<std::endl;
          ????????????????????????????????//?返回值可有可無
          ????????????????????????????});
          ????
          ????server.run();//啟動服務(wù)端
          ????
          ????return?EXIT_SUCCESS;
          }

          3)注冊異步服務(wù)

          有時因為各種原因我們無法或者不希望一個遠程調(diào)用能同步返回(比如需要等待一個線程返回),這時候只需給 register_handler 方法一個 Async 模板參數(shù)(位于 rest_rpc 命名空間):

          /*異步服務(wù)返回類型為?void*/
          void?async_greet(rpc_conn?conn,?const?std::string&?name)?{
          ????auto?req_id?=?conn.lock()->request_id();//?異步服務(wù)需要先保存請求?id

          ????//?這里新建了一個線程,代表異步處理了一些任務(wù)
          ????std::thread?thd([conn,?req_id,?name]?{
          ????????
          ????????std::string?ret?=?"Hello?"?+?name?+?",?Welcome?to?Hello?Github!";
          ????????
          ????????/*這里的?conn?是一個?weak_ptr*/
          ????????auto?conn_sp?=?conn.lock();//?使用?weak_ptr?的?lock?方法獲取一個?shared_ptr
          ????????
          ????????if?(conn_sp)?{
          ????????????/*操作完成,返回;std::move(ret)?為返回值*/
          ????????????conn_sp->pack_and_response(req_id,?std::move(ret));
          ????????}
          ????})
          ;
          ????
          ????thd.detach();
          }

          int?main(){
          ????rpc_server?server(9000,?6);
          ????
          ?server.register_handler("async_greet",?async_greet);//?使用?Async?作為模板參數(shù)
          ????
          ????server.run();//啟動服務(wù)端
          ????
          ????return?EXIT_SUCCESS;
          }

          rest_rpc 支持在同一個端口上注冊多個服務(wù),例如:

          server.register_handler("func_greet",?hello);
          server.register_handler("greet",?&test_func::hello,?&greeting);
          server.register_handler("call_lambda",?
          ????????????????????????/*除?conn?外其他參數(shù)為可變參數(shù)*/
          ????????????????????????[&server](rpc_conn?conn?/*其他參數(shù)可有可無*/)?{
          ????????????????????????????std::cout?<"Hello?Github!"?<std::endl;
          ????????????????????????????//?返回值可有可無
          ????????????????????????});
          //?其他服務(wù)等等

          server.run();

          3.3 編寫客戶端

          生成一個能進行遠程服務(wù)調(diào)用的客戶端要經(jīng)歷以下過程:

          1. rpc_client 對象實例化,設(shè)定服務(wù)端地址與端口
          2. 連接服務(wù)端
          3. 調(diào)用服務(wù)

          1)rpc_client

          rpc_clientrest_rpc 客戶端對象,有連接服務(wù)端、調(diào)用服務(wù)端服務(wù)、序列化消息、反序列化消息等功能,位于 rest_rpc 命名空間。

          使用時需要先實例化一個 rpc_client 對象,然后使用其提供的 connectasync_connect 方法來 同步/異步 的連接到服務(wù)器,如:

          rpc_client?client;

          bool?has_connected?=?client.connect("127.0.0.1",?9000);//同步連接,返回是否連接成功

          client.async_connect("127.0.0.1",?9000);//異步連接,無返回值

          當然,rpc_client 還提供了 enable_auto_reconnectenable_auto_heartbeat 功能,用于不同情況下保持連接。

          2)調(diào)用遠程服務(wù)

          rpc_client 提供了 async_callcall 兩種方式來 異步/同步 的調(diào)用遠程服務(wù),其中 async_call 又支持 callbackfuture 兩種處理返回消息的方法,這部分介紹 同步 調(diào)用方法 call

          在調(diào)用 call 方法時如果我們的服務(wù)有返回值則需要設(shè)定模板參數(shù),比如遠程服務(wù)返回一個整數(shù)需要這樣指定返回值類型 call,如果不指定則代表無返回值。

          編寫服務(wù)端 部分我們說過每個服務(wù)在注冊的時候都有一個名字,通過名字可以進行遠程服務(wù)的調(diào)用,現(xiàn)在我們調(diào)用 服務(wù)端 部分寫的第一個例子:

          int?main(){
          ????/*?rest_rpc?在遇到錯誤(調(diào)用服務(wù)傳入?yún)?shù)和遠程服務(wù)需要參數(shù)不一致、連接失敗等)時會拋出異常*/
          ????try{

          ????????/*建立連接*/
          ????????rpc_client?client("127.0.0.1",?9000);//?IP?地址,端口號
          ????????/*設(shè)定超時?5s(不填默認為?3s),connect?超時返回?false,成功返回?true*/
          ????????bool?has_connected?=?client.connect(5);
          ????????/*沒有建立連接則退出程序*/
          ????????if?(!has_connected)?{
          ????????????std::cout?<"connect?timeout"?<std::endl;
          ????????????exit(-1);
          ????????}

          ????????/*調(diào)用遠程服務(wù),返回歡迎信息*/
          ????????std::string?result?=?client.call<std::string>("func_greet",?"HG");//?func_greet?為事先注冊好的服務(wù)名,需要一個?name?參數(shù),這里為?Hello?Github?的縮寫?HG
          ????????std::cout?<std::endl;

          ????}
          ????/*遇到連接錯誤、調(diào)用服務(wù)時參數(shù)不對等情況會拋出異常*/
          ????catch?(const?std::exception?&?e)?{
          ????????std::cout?<std::endl;
          ????}
          ????
          ????return?EXIT_SUCCESS;
          }

          當然,有些調(diào)用也許沒有任何消息返回,這是時候直接使用 client.call("xxx", ...) 即可,此時 call 方法返回類型為 void。

          3)異步調(diào)用遠程服務(wù)

          有些時候我們調(diào)用的遠程服務(wù)由于各種原因需要一些時間才能返回,這時候可以使用 rpc_client 提供的異步調(diào)用方法 async_call ,它默認為 callback 模式,模板參數(shù)為 timeout 時間,如想要使用 future 模式則需要特別指定。

          callback 模式,回調(diào)函數(shù)形參要與例程中一樣,在調(diào)用之后需要加上 client.run()

          /*默認為?call?back?模式,模板參數(shù)代表?timeout?2000ms,async_call?參數(shù)順序為?服務(wù)名,?回調(diào)函數(shù),?調(diào)用服務(wù)需要的參數(shù)(數(shù)目類型不定)*/
          /*timeout?不指定則默認為?5s,設(shè)定為?0?代表不檢查?timeout?*/
          client.async_call<2000>("async_greet",?
          ??????????????????/*在遠程服務(wù)返回時自動調(diào)用該回調(diào)函數(shù),注意形參只能這樣寫*/
          ??????????????????[&client](const?boost::system::error_code?&?ec,?string_view?data)?{
          ????????????????????????
          ????????????????????????auto?str?=?as<std::string>(data);
          ????????????????????????std::cout?<std::endl;
          ???????????????????},?
          ??????????????????"HG");//?echo?服務(wù)將傳入的參數(shù)直接返回
          client.run();?//?啟動服務(wù)線程,等待返回

          //?其余部分和?call?的使用方法一樣

          Future 模式:

          auto?f?=?client.async_call("async_greet",?"HG");

          if?(f.wait_for(std::chrono::milliseconds(50))?==?std::future_status::timeout)?{
          ????std::cout?<"timeout"?<std::endl;
          }
          else?{
          ????auto?ret?=?f.get().as<std::string>();//?轉(zhuǎn)換為?string?對象,無返回值可以寫?f.get().as()
          ????std::cout?<std::endl;
          }

          3.4 序列化

          使用 rest_rpc 時如果參數(shù)是標準庫相關(guān)對象則不需要單獨指定序列化方式,如果使用自定義對象,則需要使用 msgpack 定義序列化方式,例如要傳輸這樣一個結(jié)構(gòu)體:

          struct?person?{
          ?int?id;
          ?std::string?name;
          ?int?age;
          };

          則需要加上 MSGPACK_DEFINE()

          /*
          注意:無論是服務(wù)端還是客戶端都要進行這樣的操作
          客戶端和服務(wù)端?MSGPACK_DEFINE()?中的填入的參數(shù)順序必須一致,這一點和?msgpack?的序列化方式有
          如客戶端和服務(wù)端中?MSGPACK_DEFINE()?中參數(shù)順序不一致可能會導(dǎo)致解包時發(fā)生錯誤
          */

          struct?person?{
          ?int?id;
          ?std::string?name;
          ?int?age;

          ?MSGPACK_DEFINE(id,?name,?age);//定義需要序列化的內(nèi)容
          };

          在對象中也是同理:

          class?person{
          ????private:
          ?????int?id;
          ????????std::string?name;
          ????????int?age;
          ????public:
          ?????MSGPACK_DEFINE(id,?name,?age);//需要在?public?中
          }

          然后即可將 person 作為參數(shù)類型進行使用。

          四、特點:發(fā)布/訂閱模式

          rest_rpc 的一大特色就是提供了 發(fā)布-訂閱 模式,這個模式在客戶端和服務(wù)端之間需要不停傳輸消息時非常有用。

          服務(wù)端 只需要使用 rpc_serverpublish 或者 publish_by_token 方法即可發(fā)布一條訂閱消息,其中如果使用 token 則訂閱者需要使用相同的 token 才能訪問,例如:

          int?main()?{
          ????rpc_server?server(9000,?6);

          ????std::thread?broadcast([&server]()?{
          ????????while?(true)?{
          ????????????/*發(fā)布訂閱消息,所有訂閱了?greet?的客戶端都可以獲得消息*/
          ????????????server.publish("greet",?"Hello?GitHub!");
          ????????????/*只有訂閱了?secret_greet?并且提供了?www.hellogithub.com?作為?token?才可以獲得消息*/
          ????????????server.publish_by_token("secret_greet",?"www.hellogithub.com",?"Hello?Github!?this?is?secret?message");

          ????????????std::this_thread::sleep_for(std::chrono::seconds(1));//?等待一秒
          ????????}
          ????})
          ;

          ????server.run();//啟動服務(wù)端

          ????return?EXIT_SUCCESS;
          }

          客戶端 只需使用 rpc_client 的 ?subscribe 方法即可:

          void?test_subscribe()?{
          ????rpc_client?client;

          ????client.enable_auto_reconnect();//?自動重連
          ????client.enable_auto_heartbeat();//?自動心跳包
          ????bool?r?=?client.connect("127.0.0.1",?9000);
          ????if?(!r)?{
          ????????return;
          ????}

          ????//?直接訂閱,無?token
          ????client.subscribe("greet",?[](string_view?data)?{
          ????????std::cout?<std::endl;
          ????????});
          ????//?需要?token?才能正常獲得訂閱消息
          ????client.subscribe("secret_greet",?"www.hellogithub.com",?[](string_view?data)?{
          ????????std::cout?<std::endl;
          ????????});
          ????
          ????client.run();//?不斷運行
          }

          int?main()?{
          ????
          ????test_subscribe();

          ????return?EXIT_SUCCESS;
          }

          1)訂閱時傳輸自定義對象

          如果有這樣一個對象需要傳輸:

          struct?person?{
          ?int?id;
          ?std::string?name;
          ?int?age;

          ?MSGPACK_DEFINE(id,?name,?age);
          };

          服務(wù)端 直接將其作為一個參數(shù)即可,例如:

          person?p{?1,?"tom",?20?};
          server.publish("key",?p);

          客戶端 需要進行 反序列化

          client.subscribe("key",?
          ?????????????????[](string_view?data)?{
          ?????????????????????msgpack_codec?codec;
          ?????????????????????
          ?????????????????????person?p?=?codec.unpack(data.data(),?data.size());
          ?????????????????????std::cout?<std::endl;
          ?????????????????});

          五、最后

          RPC 有很多成熟的工業(yè)框架如:

          • 谷歌的 grpc
          • 百度的 brpc 等

          但是相較 rest_rpc 來講配置和使用較為復(fù)雜。新手將 rest_rpc 作為 RPC 的入門項目是一個非常好的選擇。

          至此,相信你已經(jīng)掌握了 rest_rpc 的絕大部分功能,那么是時候動手搞一個 RPC 服務(wù)啦!

          點擊關(guān)注第一時間收到推送


          ▼ 點擊?閱讀原文?投稿

          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一区xxx | 上床视频免费网站 | 秋霞丝鲁片一区二区三区手机在绒免 | 色五月婷婷俺来也 | 自拍偷拍中文字幕 |