探索C++中的輕量級(jí)RPC:打造高性能網(wǎng)絡(luò)通信
在當(dāng)今數(shù)字化浪潮中,分布式系統(tǒng)已成為構(gòu)建大規(guī)模、高性能應(yīng)用的基石。而遠(yuǎn)程過程調(diào)用(RPC),作為分布式系統(tǒng)中的關(guān)鍵技術(shù),宛如一座橋梁,連接著不同服務(wù)器上的服務(wù),使得它們能夠協(xié)同工作,為用戶提供無縫的體驗(yàn)。想象一下,你正在使用一款熱門的在線購(gòu)物應(yīng)用。當(dāng)你點(diǎn)擊 “加入購(gòu)物車” 按鈕時(shí),背后的系統(tǒng)需要與多個(gè)服務(wù)進(jìn)行交互,包括庫(kù)存管理服務(wù)、用戶信息服務(wù)等。RPC 就像一位幕后英雄,讓這些分布在不同服務(wù)器上的服務(wù)之間的通信變得簡(jiǎn)單高效,仿佛它們都運(yùn)行在同一臺(tái)機(jī)器上。
然而,構(gòu)建一個(gè)高效、可靠的 RPC 框架并非易事。不同的編程語言、網(wǎng)絡(luò)環(huán)境、服務(wù)需求等,都給 RPC 的實(shí)現(xiàn)帶來了巨大的挑戰(zhàn)。今天,我們就來探討如何用 C++ 打造一個(gè)輕量級(jí)的 RPC 分布式網(wǎng)絡(luò)通信框架,看看它是如何在復(fù)雜的環(huán)境中實(shí)現(xiàn)高效通信的。
一、RPC分布式簡(jiǎn)介
1.1概述
RPC,即遠(yuǎn)程過程調(diào)用(Remote Procedure Call) ,是一種讓程序在不同計(jì)算機(jī)之間像調(diào)用本地函數(shù)一樣進(jìn)行通信的技術(shù)。打個(gè)比方,你去餐廳點(diǎn)餐,服務(wù)員就像是本地調(diào)用,你直接告訴服務(wù)員你想吃什么,他能馬上響應(yīng)。而如果這家餐廳的廚房在另一個(gè)地方,你通過對(duì)講機(jī)向廚房點(diǎn)餐,這個(gè)過程就類似 RPC。你不需要知道對(duì)講機(jī)是如何工作、信號(hào)如何傳輸?shù)模灰窈蜕磉叺姆?wù)員溝通一樣點(diǎn)餐就行。在分布式系統(tǒng)中,不同的服務(wù)可能部署在不同的服務(wù)器上,RPC 就像是這個(gè) “對(duì)講機(jī)”,讓不同服務(wù)器上的服務(wù)之間能夠輕松地進(jìn)行通信和交互。
在分布式系統(tǒng)中,各個(gè)服務(wù)分布在不同的節(jié)點(diǎn)上,為了實(shí)現(xiàn)它們之間的協(xié)同工作,進(jìn)程間通信至關(guān)重要。傳統(tǒng)的進(jìn)程間通信方式,如 Socket,需要開發(fā)者深入了解網(wǎng)絡(luò)編程細(xì)節(jié),包括連接建立、數(shù)據(jù)傳輸、序列化與反序列化等,這無疑增加了開發(fā)的難度和復(fù)雜性。而 RPC 則通過將遠(yuǎn)程調(diào)用抽象成類似本地調(diào)用的形式,極大地簡(jiǎn)化了分布式系統(tǒng)的開發(fā)過程。開發(fā)者可以專注于業(yè)務(wù)邏輯的實(shí)現(xiàn),而無需過多關(guān)注底層網(wǎng)絡(luò)通信的細(xì)節(jié),從而提高開發(fā)效率,降低出錯(cuò)的概率。RPC通信框架的大致結(jié)構(gòu)流程圖如下:
圖片
⑴ZooKeeper
ZooKeeper在這里作為服務(wù)方法的管理配置中心,負(fù)責(zé)管理服務(wù)方法提供者對(duì)外提供的服務(wù)方法。服務(wù)方法提供者提前將本端對(duì)外提供的服務(wù)方法名及自己的通信地址信息(IP:Port)注冊(cè)到ZooKeeper。當(dāng)Caller發(fā)起遠(yuǎn)端調(diào)用時(shí),會(huì)先拿著自己想要調(diào)用的服務(wù)方法名詢問ZooKeeper,ZooKeeper告知Caller想要調(diào)用的服務(wù)方法在哪臺(tái)服務(wù)器上(ZooKeeper返回目標(biāo)服務(wù)器的IP:Port給Caller),Caller便向目標(biāo)服務(wù)器Callee請(qǐng)求服務(wù)方法調(diào)用。服務(wù)方在本地執(zhí)行相應(yīng)服務(wù)方法后將結(jié)果返回給Caller。
⑵ProtoBuf
ProtoBuf能提供對(duì)數(shù)據(jù)的序列化和反序列化,ProtoBuf可以用于結(jié)構(gòu)化數(shù)據(jù)的串行序列化,并且以Key-Value格式存儲(chǔ)數(shù)據(jù),因?yàn)椴捎枚M(jìn)制格,所以序列化出來的數(shù)據(jù)比較少,作為網(wǎng)絡(luò)傳輸?shù)妮d體效率很高。
Caller和Callee之間的數(shù)據(jù)交互就是借助ProtoBuf完成,具體的使用方法和細(xì)節(jié)后面會(huì)進(jìn)一步拓展。
⑶Muduo
Muduo庫(kù)是基于(Multi-)Reactor模型的多線程網(wǎng)絡(luò)庫(kù),在RPC通信框架中涉及到網(wǎng)絡(luò)通信。另外我們可以服務(wù)提供方實(shí)現(xiàn)為IO多線程,實(shí)現(xiàn)高并發(fā)處理遠(yuǎn)端服務(wù)方法請(qǐng)求。
1.2常見RPC 框架
在 RPC 的世界里,有許多優(yōu)秀的框架,它們各有千秋。
gRPC,由 Google 開發(fā)并開源,基于 HTTP/2 協(xié)議和 Protocol Buffers(Protobuf)序列化協(xié)議。它就像一個(gè)全能選手,支持多種編程語言,如 Java、Go、C++、Python 等?;?HTTP/2,它具備雙向流、多路復(fù)用、頭部壓縮和請(qǐng)求優(yōu)先級(jí)等特性,傳輸效率極高。使用 Protobuf 作為序列化協(xié)議,使得數(shù)據(jù)傳輸高效且安全。gRPC 適用于對(duì)性能要求極高、需要跨語言支持和強(qiáng)類型約束的分布式系統(tǒng),比如大規(guī)?;ヂ?lián)網(wǎng)應(yīng)用、金融系統(tǒng)等。
Thrift,由 Facebook 開發(fā)并捐贈(zèng)給 Apache,是一個(gè)跨語言的高效 RPC 框架。它支持多種序列化格式和傳輸協(xié)議,擴(kuò)展性超強(qiáng)。就像一個(gè)百變星君,能適應(yīng)各種復(fù)雜的多語言環(huán)境,在數(shù)據(jù)平臺(tái)、異構(gòu)服務(wù)集成等場(chǎng)景中表現(xiàn)出色。
Dubbo阿里巴巴開源的高性能 Java RPC 框架,主要用于構(gòu)建微服務(wù)架構(gòu)中的服務(wù)調(diào)用和治理。它支持多種通信協(xié)議,具備強(qiáng)大的服務(wù)治理能力,如服務(wù)注冊(cè)與發(fā)現(xiàn)、負(fù)載均衡、限流、熔斷降級(jí)等。Dubbo 就像是一位貼心的管家,在 Java 技術(shù)棧下的高性能微服務(wù)架構(gòu)中,尤其是需要復(fù)雜服務(wù)治理功能的企業(yè)應(yīng)用中,發(fā)揮著重要作用 。
二、C++實(shí)現(xiàn)RPC分布式原理
2.1性能卓越:快人一步
C++ 以其卓越的性能在編程語言中獨(dú)樹一幟,這一特性在構(gòu)建 RPC 分布式網(wǎng)絡(luò)通信框架時(shí)更是發(fā)揮得淋漓盡致。從執(zhí)行效率來看,C++ 作為一種編譯型語言,能夠直接將代碼編譯成機(jī)器碼,這使得程序在運(yùn)行時(shí)無需像解釋型語言那樣進(jìn)行逐行解釋,大大減少了運(yùn)行時(shí)的開銷。在對(duì)實(shí)時(shí)性要求極高的金融交易系統(tǒng)中,每毫秒的延遲都可能導(dǎo)致巨大的損失。使用 C++ 實(shí)現(xiàn)的 RPC 框架,能夠快速處理大量的交易請(qǐng)求,確保交易信號(hào)的及時(shí)傳遞和執(zhí)行,為系統(tǒng)的高效運(yùn)行提供了堅(jiān)實(shí)保障。
C++ 在資源利用方面也表現(xiàn)出色。它賦予開發(fā)者對(duì)內(nèi)存的精細(xì)控制權(quán),開發(fā)者可以根據(jù)實(shí)際需求精確地分配和釋放內(nèi)存,避免了內(nèi)存泄漏和不必要的內(nèi)存占用。這在分布式系統(tǒng)中尤為重要,因?yàn)榉植际较到y(tǒng)通常需要處理大量的數(shù)據(jù)和并發(fā)請(qǐng)求,對(duì)內(nèi)存的合理利用能夠有效提升系統(tǒng)的整體性能和穩(wěn)定性。以一個(gè)大規(guī)模的電商系統(tǒng)為例,在促銷活動(dòng)期間,系統(tǒng)會(huì)面臨海量的用戶請(qǐng)求,C++ 實(shí)現(xiàn)的 RPC 框架能夠高效地管理內(nèi)存,確保系統(tǒng)在高并發(fā)的情況下依然能夠穩(wěn)定運(yùn)行,為用戶提供流暢的購(gòu)物體驗(yàn)。
有數(shù)據(jù)表明,在處理大規(guī)模數(shù)據(jù)傳輸和復(fù)雜計(jì)算任務(wù)時(shí),C++ 實(shí)現(xiàn)的 RPC 框架相比一些其他語言實(shí)現(xiàn)的框架,性能提升可達(dá) 30% - 50%。這一顯著的性能優(yōu)勢(shì),使得 C++ 成為追求高性能 RPC 框架的首選語言 。
2.2靈活定制:量體裁衣
C++ 的強(qiáng)大可定制性,使其能夠像一位技藝精湛的裁縫,根據(jù)不同場(chǎng)景的特殊需求,為 RPC 框架量身定制解決方案。
在通信協(xié)議方面,C++ 給予開發(fā)者極大的自由度。開發(fā)者可以根據(jù)應(yīng)用場(chǎng)景的特點(diǎn),如數(shù)據(jù)量大小、傳輸頻率、網(wǎng)絡(luò)環(huán)境等,選擇或設(shè)計(jì)最適合的通信協(xié)議。在網(wǎng)絡(luò)環(huán)境復(fù)雜、帶寬有限的情況下,開發(fā)者可以設(shè)計(jì)一種輕量級(jí)的自定義通信協(xié)議,減少數(shù)據(jù)傳輸?shù)拈_銷,提高傳輸效率。而對(duì)于對(duì)安全性要求極高的場(chǎng)景,開發(fā)者可以基于現(xiàn)有的安全協(xié)議,如 SSL/TLS,進(jìn)行定制化開發(fā),確保數(shù)據(jù)在傳輸過程中的安全性。
對(duì)于數(shù)據(jù)序列化和反序列化方式,C++ 同樣提供了豐富的選擇。常見的序列化方式如 JSON、XML、Protocol Buffers 等,都可以在 C++ 中輕松實(shí)現(xiàn)。開發(fā)者可以根據(jù)數(shù)據(jù)的結(jié)構(gòu)和應(yīng)用場(chǎng)景的需求,選擇最適合的序列化方式。如果數(shù)據(jù)結(jié)構(gòu)較為復(fù)雜,且對(duì)傳輸效率要求較高,Protocol Buffers 可能是一個(gè)不錯(cuò)的選擇,因?yàn)樗軌驅(qū)?shù)據(jù)高效地編碼為二進(jìn)制格式,減少數(shù)據(jù)傳輸?shù)拇笮?,提高傳輸速度。而如果?shù)據(jù)需要與其他系統(tǒng)進(jìn)行交互,且對(duì)可讀性有一定要求,JSON 則可能更為合適,因?yàn)樗母袷捷^為簡(jiǎn)潔,易于閱讀和解析。
在不同的應(yīng)用場(chǎng)景中,C++ 的靈活定制性得到了充分的體現(xiàn)。在游戲開發(fā)中,由于游戲?qū)?shí)時(shí)性和性能要求極高,開發(fā)者可以使用 C++ 定制一個(gè)高效的 RPC 框架,優(yōu)化網(wǎng)絡(luò)通信,減少延遲,為玩家提供流暢的游戲體驗(yàn)。在工業(yè)自動(dòng)化領(lǐng)域,由于不同的設(shè)備和系統(tǒng)具有不同的通信需求,C++ 的可定制性使得開發(fā)者能夠?yàn)槊總€(gè)設(shè)備和系統(tǒng)定制專屬的 RPC 框架,實(shí)現(xiàn)設(shè)備之間的高效通信和協(xié)同工作 。
三、核心實(shí)現(xiàn)步驟
3.1基礎(chǔ)搭建:打牢根基
在構(gòu)建這個(gè)輕量級(jí) RPC 分布式網(wǎng)絡(luò)通信框架時(shí),我們需要一些趁手的工具,ZooKeeper、ProtoBuf 和 Muduo 庫(kù)便是我們的得力助手。
ZooKeeper客戶端(Callee)首先將Watcher注冊(cè)到服務(wù)端,同時(shí)把Watcher對(duì)象保存到客戶端的Watcher管理器中。當(dāng)ZooKeeper服務(wù)端監(jiān)聽到ZooKeeper中的數(shù)據(jù)狀態(tài)發(fā)生變化時(shí),服務(wù)端主動(dòng)通知客戶端(告知客戶端事件類型和狀態(tài)類型),接著客戶端的Watch管理器會(huì)觸發(fā)相關(guān)Watcher來回調(diào)相應(yīng)處理邏輯(GlobalWatcher
),從而完成整體的數(shù)據(jù)發(fā)布/訂閱流程。
Watcher的設(shè)置和獲取在開發(fā)中很常見,不同的操作會(huì)收到不同的watcher信息。更多內(nèi)容還是自行g(shù)oogle吧,我自己還只有半桶水的功夫。日后會(huì)繼續(xù)學(xué)習(xí),專門對(duì)ZooKeeper做一個(gè)全面細(xì)致的剖析。
ProtoBuf,即 Protocol Buffers,是 Google 開發(fā)的一種數(shù)據(jù)序列化協(xié)議。它就像一個(gè)高效的翻譯官,能夠?qū)⒔Y(jié)構(gòu)化數(shù)據(jù)進(jìn)行序列化和反序列化。在我們的框架中,客戶端和服務(wù)端之間的數(shù)據(jù)交互就是借助 ProtoBuf 完成的。它采用二進(jìn)制格式存儲(chǔ)數(shù)據(jù),序列化出來的數(shù)據(jù)量少,作為網(wǎng)絡(luò)傳輸?shù)妮d體效率極高。比如在定義一個(gè)用戶登錄請(qǐng)求時(shí),使用 ProtoBuf 可以將用戶名和密碼等信息高效地編碼為二進(jìn)制格式進(jìn)行傳輸,在接收端又能快速地解碼還原 。
Muduo 庫(kù)是基于 (Multi-) Reactor 模型的多線程網(wǎng)絡(luò)庫(kù),在 RPC 通信框架中主要負(fù)責(zé)網(wǎng)絡(luò)通信部分。它可以將服務(wù)提供方實(shí)現(xiàn)為 IO 多線程,從而實(shí)現(xiàn)高并發(fā)處理遠(yuǎn)端服務(wù)方法請(qǐng)求。在處理大量客戶端同時(shí)請(qǐng)求服務(wù)的場(chǎng)景中,Muduo 庫(kù)能夠高效地管理網(wǎng)絡(luò)連接和數(shù)據(jù)傳輸,確保系統(tǒng)的穩(wěn)定性和高性能 。
3.2業(yè)務(wù)層實(shí)現(xiàn):注入靈魂
以一個(gè)簡(jiǎn)單的用戶登錄和注冊(cè)業(yè)務(wù)場(chǎng)景為例,我們來看看如何用 ProtoBuf 定義數(shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)業(yè)務(wù)層代碼。
首先,使用 ProtoBuf 定義數(shù)據(jù)結(jié)構(gòu)。假設(shè)我們有一個(gè)用戶登錄的場(chǎng)景,需要定義登錄請(qǐng)求消息體和登錄響應(yīng)消息體。在.proto 文件中,可以這樣定義:
syntax = "proto3";
package user;
message LoginRequest {
string username = 1;
string password = 2;
}
message LoginResponse {
bool success = 1;
string message = 2;
}
service UserService {
rpc Login(LoginRequest) returns (LoginResponse);
}
在這段代碼中,我們定義了LoginRequest消息體,包含username和password兩個(gè)字段,用于客戶端向服務(wù)端發(fā)送登錄請(qǐng)求。LoginResponse消息體則包含success字段表示登錄是否成功,message字段用于返回提示信息。UserService服務(wù)定義了一個(gè)Login方法,接受LoginRequest并返回LoginResponse 。
然后,使用protoc工具編譯這個(gè).proto 文件,生成對(duì)應(yīng)的 C++ 代碼。編譯命令如下:
protoc --cpp_out=. user.proto
編譯后會(huì)生成user.pb.h和http://user.pb.cc文件,其中包含了用于 C++ 程序使用的類和方法。在客戶端代碼中,可以這樣調(diào)用服務(wù)端的Login方法:
#include <iostream>
#include "mprpcapplication.h"
#include "user.pb.h"
#include "mprpcchannel.h"
int main(int argc, char** argv) {
MprpcApplication::Init(argc, argv);
user::UserService_Stub stub(new MprpcChannel());
user::LoginRequest request;
request.set_username("張三");
request.set_password("123456");
user::LoginResponse response;
stub.Login(nullptr, &request, &response, nullptr);
if (response.success()) {
std::cout << "登錄成功" << std::endl;
} else {
std::cout << "登錄失敗: " << response.message() << std::endl;
}
return 0;
}
在這段代碼中,我們首先初始化MprpcApplication,然后創(chuàng)建UserService_Stub對(duì)象,并設(shè)置登錄請(qǐng)求的參數(shù)。接著調(diào)用Login方法,將請(qǐng)求發(fā)送到服務(wù)端,并獲取響應(yīng)結(jié)果。根據(jù)響應(yīng)結(jié)果判斷登錄是否成功 。
3.3服務(wù)端構(gòu)建:撐起后臺(tái)
在服務(wù)端,我們需要?jiǎng)?chuàng)建一個(gè)RpcProvider類,來實(shí)現(xiàn)服務(wù)的注冊(cè)與發(fā)布。
首先,定義RpcProvider類的基本結(jié)構(gòu)。在rpcprovider.h文件中,可以這樣定義:
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <unordered_map>
#include <google/protobuf/service.h>
class RpcProvider {
public:
void NotifyService(google::protobuf::Service* service);
void Run();
private:
muduo::net::EventLoop* m_eventLoop;
muduo::net::TcpServer* m_tcpServer;
std::unordered_map<std::string, google::protobuf::Service*> m_serviceMap;
};
在這個(gè)類中,NotifyService方法用于注冊(cè)服務(wù),Run方法用于啟動(dòng)服務(wù)端。m_eventLoop和m_tcpServer分別用于管理事件循環(huán)和創(chuàng)建 TCP 服務(wù)器。m_serviceMap用于存儲(chǔ)注冊(cè)的服務(wù) 。
然后,實(shí)現(xiàn)NotifyService方法。在http://rpcprovider.cc文件中,代碼如下:
void RpcProvider::NotifyService(google::protobuf::Service* service) {
google::protobuf::ServiceDescriptor* pserviceDesc = service->GetDescriptor();
std::string service_name = pserviceDesc->name();
m_serviceMap[service_name] = service;
std::cout << "發(fā)布服務(wù): " << service_name << std::endl;
}
在這個(gè)方法中,我們首先獲取服務(wù)的描述信息,然后將服務(wù)名稱和服務(wù)對(duì)象存儲(chǔ)到m_serviceMap中 。
最后,實(shí)現(xiàn)Run方法。代碼如下:
void RpcProvider::Run() {
std::string ip = MprpcApplication::GetInstance().GetConfig().Load("rpcserverip");
uint16_t port = atoi(MprpcApplication::GetInstance().GetConfig().Load("rpcserverport").c_str());
muduo::net::InetAddress address(ip, port);
m_eventLoop = new muduo::net::EventLoop();
m_tcpServer = new muduo::net::TcpServer(m_eventLoop, address, "RpcProvider");
m_tcpServer->setConnectionCallback(std::bind(&RpcProvider::OnConnection, this, std::placeholders::_1));
m_tcpServer->setMessageCallback(std::bind(&RpcProvider::OnMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
m_tcpServer->setThreadNum(4);
std::cout << "RpcProvider start service at ip: " << ip << " port: " << port << std::endl;
m_tcpServer->start();
m_eventLoop->loop();
}
在這個(gè)方法中,我們首先從配置文件中讀取服務(wù)端的 IP 和端口,然后創(chuàng)建InetAddress對(duì)象和TcpServer對(duì)象。接著設(shè)置連接回調(diào)函數(shù)和消息回調(diào)函數(shù),并設(shè)置線程數(shù)量。最后啟動(dòng)服務(wù)器,開始監(jiān)聽客戶端的請(qǐng)求 。
3.4客戶端構(gòu)建:打造前臺(tái)
在客戶端,我們需要?jiǎng)?chuàng)建一個(gè)RpcChannel類,來實(shí)現(xiàn)客戶端對(duì)服務(wù)端的遠(yuǎn)程調(diào)用。
首先,定義RpcChannel類的基本結(jié)構(gòu)。在rpcchannel.h文件中,可以這樣定義:
#include <google/protobuf/channel.h>
#include <google/protobuf/message.h>
#include <muduo/net/TcpClient.h>
#include <muduo/net/EventLoop.h>
class RpcChannel : public google::protobuf::RpcChannel {
public:
RpcChannel(muduo::net::EventLoop* loop, const std::string& ip, uint16_t port);
void CallMethod(const google::protobuf::MethodDescriptor* method,
google::protobuf::RpcController* controller,
const google::protobuf::Message* request,
google::protobuf::Message* response,
google::protobuf::Closure* done) override;
private:
muduo::net::TcpClient* m_tcpClient;
muduo::net::EventLoop* m_eventLoop;
std::string m_ip;
uint16_t m_port;
};
在這個(gè)類中,RpcChannel構(gòu)造函數(shù)用于初始化客戶端,CallMethod方法用于實(shí)現(xiàn)遠(yuǎn)程調(diào)用。m_tcpClient和m_eventLoop分別用于管理 TCP 客戶端和事件循環(huán)。m_ip和m_port用于存儲(chǔ)服務(wù)端的 IP 和端口 。
然后,實(shí)現(xiàn)RpcChannel類的構(gòu)造函數(shù)。在http://rpcchannel.cc文件中,代碼如下:
RpcChannel::RpcChannel(muduo::net::EventLoop* loop, const std::string& ip, uint16_t port)
: m_eventLoop(loop), m_ip(ip), m_port(port) {
m_tcpClient = new muduo::net::TcpClient(m_eventLoop, muduo::net::InetAddress(m_ip, m_port));
m_tcpClient->enableRetry();
}
在這個(gè)構(gòu)造函數(shù)中,我們創(chuàng)建TcpClient對(duì)象,并設(shè)置自動(dòng)重試功能 。
最后,實(shí)現(xiàn)CallMethod方法。代碼如下:
void RpcChannel::CallMethod(const google::protobuf::MethodDescriptor* method,
google::protobuf::RpcController* controller,
const google::protobuf::Message* request,
google::protobuf::Message* response,
google::protobuf::Closure* done) {
std::string service_name = method->service()->full_name();
std::string method_name = method->name();
uint32_t args_size = request->ByteSizeLong();
std::string args_str;
request->SerializeToString(&args_str);
muduo::net::TcpConnectionPtr conn = m_tcpClient->getConnection();
if (conn) {
conn->send(service_name + method_name + std::to_string(args_size) + args_str);
conn->setReadCallback([this, response, done](const muduo::net::TcpConnectionPtr&, muduo::net::Buffer* buffer) {
std::string response_str = buffer->retrieveAllAsString();
response->ParseFromString(response_str);
done->Run();
});
}
}
在這個(gè)方法中,我們首先獲取服務(wù)名稱和方法名稱,然后將請(qǐng)求數(shù)據(jù)序列化并發(fā)送到服務(wù)端。接著設(shè)置讀取回調(diào)函數(shù),當(dāng)接收到服務(wù)端的響應(yīng)數(shù)據(jù)時(shí),將其反序列化并存儲(chǔ)到response中,最后調(diào)用done回調(diào)函數(shù) 。
四、實(shí)例展示:眼見為實(shí)
4.1完整代碼剖析
下面是一個(gè)完整的簡(jiǎn)單示例代碼,用于更清晰地展示 C++ 實(shí)現(xiàn)的輕量級(jí) RPC 分布式網(wǎng)絡(luò)通信框架的運(yùn)行機(jī)制。
首先,定義服務(wù)接口。在user.proto文件中,定義如下:
syntax = "proto3";
package user;
message LoginRequest {
string username = 1;
string password = 2;
}
message LoginResponse {
bool success = 1;
string message = 2;
}
service UserService {
rpc Login(LoginRequest) returns (LoginResponse);
}
在這個(gè)文件中,我們定義了LoginRequest消息體,包含用戶名和密碼兩個(gè)字段,用于客戶端向服務(wù)端發(fā)送登錄請(qǐng)求。LoginResponse消息體則包含登錄是否成功的標(biāo)志以及相應(yīng)的提示信息。UserService服務(wù)定義了一個(gè)Login方法,接受LoginRequest類型的請(qǐng)求,并返回LoginResponse類型的響應(yīng)。
接著,使用protoc工具編譯user.proto文件,生成對(duì)應(yīng)的 C++ 代碼。編譯命令如下:
protoc --cpp_out=. user.proto
編譯后會(huì)生成user.pb.h和http://user.pb.cc文件,這兩個(gè)文件包含了用于 C++ 程序使用的類和方法,是實(shí)現(xiàn) RPC 通信的基礎(chǔ)。
服務(wù)端代碼如下:
#include <iostream>
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <google/protobuf/service.h>
#include "user.pb.h"
#include "rpcprovider.h"
class UserServiceImpl : public user::UserService::Service {
public:
void Login(::google::protobuf::RpcController* controller,
const ::user::LoginRequest* request,
::user::LoginResponse* response,
::google::protobuf::Closure* done) override {
std::string username = request->username();
std::string password = request->password();
if (username == "admin" && password == "123456") {
response->set_success(true);
response->set_message("登錄成功");
} else {
response->set_success(false);
response->set_message("用戶名或密碼錯(cuò)誤");
}
done->Run();
}
};
int main(int argc, char* argv[]) {
RpcProvider provider;
provider.NotifyService(new UserServiceImpl());
provider.Run();
return 0;
}
在這段服務(wù)端代碼中,我們定義了UserServiceImpl類,繼承自u(píng)ser::UserService::Service,實(shí)現(xiàn)了Login方法。在Login方法中,根據(jù)接收到的用戶名和密碼進(jìn)行驗(yàn)證,并設(shè)置相應(yīng)的響應(yīng)結(jié)果。main函數(shù)中,創(chuàng)建RpcProvider對(duì)象,注冊(cè)UserServiceImpl服務(wù),并啟動(dòng)服務(wù)端。
客戶端代碼如下:
#include <iostream>
#include "mprpcapplication.h"
#include "user.pb.h"
#include "mprpcchannel.h"
int main(int argc, char** argv) {
MprpcApplication::Init(argc, argv);
user::UserService_Stub stub(new MprpcChannel());
user::LoginRequest request;
request.set_username("admin");
request.set_password("123456");
user::LoginResponse response;
stub.Login(nullptr, &request, &response, nullptr);
if (response.success()) {
std::cout << "登錄成功: " << response.message() << std::endl;
} else {
std::cout << "登錄失敗: " << response.message() << std::endl;
}
return 0;
}
客戶端代碼中,首先初始化MprpcApplication,然后創(chuàng)建UserService_Stub對(duì)象,并設(shè)置登錄請(qǐng)求的參數(shù)。接著調(diào)用Login方法,將請(qǐng)求發(fā)送到服務(wù)端,并獲取響應(yīng)結(jié)果。最后根據(jù)響應(yīng)結(jié)果輸出相應(yīng)的信息。
4.2運(yùn)行效果呈現(xiàn)
運(yùn)行服務(wù)端代碼后,服務(wù)端會(huì)啟動(dòng)并監(jiān)聽指定的端口,等待客戶端的請(qǐng)求。當(dāng)客戶端代碼運(yùn)行時(shí),它會(huì)向服務(wù)端發(fā)送一個(gè)登錄請(qǐng)求,攜帶用戶名和密碼。服務(wù)端接收到請(qǐng)求后,進(jìn)行驗(yàn)證,并返回相應(yīng)的響應(yīng)。
在客戶端的控制臺(tái)輸出中,我們可以看到:
登錄成功: 登錄成功
這表明客戶端成功調(diào)用了服務(wù)端的Login方法,并且服務(wù)端驗(yàn)證通過,返回了成功的響應(yīng)。如果將客戶端的用戶名或密碼修改為錯(cuò)誤的值,如:
request.set_username("admin");
request.set_password("wrongpassword");
再次運(yùn)行客戶端代碼,控制臺(tái)輸出將變?yōu)椋?/p>
登錄失敗: 用戶名或密碼錯(cuò)誤
通過這樣的運(yùn)行結(jié)果,我們可以直觀地驗(yàn)證 RPC 框架的正確性和有效性??蛻舳四軌蛳裾{(diào)用本地函數(shù)一樣,方便地調(diào)用服務(wù)端的方法,并且能夠正確地傳遞參數(shù)和獲取返回結(jié)果。這充分展示了 C++ 實(shí)現(xiàn)的輕量級(jí) RPC 分布式網(wǎng)絡(luò)通信框架在簡(jiǎn)化分布式系統(tǒng)開發(fā)方面的強(qiáng)大能力 。