1. RPC 是什么?
RPC(Remote Procedure Call,远程过程调用)是一种技术,允许计算机程序调用另一台机器(服务器)上的程序或服务,而看起来就像是本地过程调用一样。RPC 抽象了底层的网络通信过程,使得开发者可以不用详细处理网络交互的细节,直接进行函数或方法的调用。
2. 为什么要用 RPC?
- 分布式系统开发简化:RPC 允许开发者在构建分布式系统时,像调用本地对象一样调用远程服务,简化了开发过程。
- 语言无关性:多数RPC框架支持多种编程语言,使得不同语言编写的服务可以互相通信。
- 效率和抽象:RPC 抽象了通信细节,开发者可以专注于业务逻辑,同时RPC的设计通常优化了通信过程,提高远程调用效率。
3. 使用 RPC 的好处
- 封装复杂性:RPC 隐藏了网络请求、数据序列化和反序列化的复杂性。
- 提高开发效率:开发者可以如同进行本地函数调用一样进行远程调用,减少了学习和开发的复杂度。
- 增强服务的可维护性:通过定义清晰的服务接口,RPC支持更好的服务版本控制和维护。
4. RPC 由哪几部分组成?
- 客户端(Client):发起远程过程调用的一方。
- 服务器端(Server):接收远程过程调用请求并处理的一方。
- 通信协议:定义了客户端和服务器之间如何交换信息的规则。
- 序列化机制:将对象或数据结构转换为可通过网络传输的格式的过程。
- 网络传输:负责数据的发送和接收。
5. 示例:一个简单的 RPC 工程代码
这里使用 gRPC 和 Protobuf,gRPC 是一个高性能、开源和通用的 RPC 框架,由 Google 主导开发,支持多种编程语言。
步骤 1: 定义服务
首先,定义一个简单的服务,服务中有一个方法,返回客户端发送的消息。
service.proto
:
syntax = "proto3";
package example;
// 定义一个 Greeter 服务
service Greeter {
// 发送一个 Greeting 消息, 返回一个 GreetingResponse
rpc Greet (Greeting) returns (GreetingResponse);
}
// Greeting 消息格式
message Greeting {
string name = 1;
}
// GreetingResponse 消息格式
message GreetingResponse {
string message = 1;
}
步骤 2: 生成服务代码
使用 protobuf 工具生成 C++ 代码。
protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` service.proto
步骤 3: 实现服务端
server.cpp
:
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "service.grpc.pb.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using example::Greeter;
using example::Greeting;
using example::GreetingResponse;
// 服务实现
class GreeterServiceImpl final : public Greeter::Service {
Status Greet(ServerContext* context, const Greeting* request,
GreetingResponse* reply) override {
std::string prefix("Hello ");
reply->set_message(prefix + request->name());
return Status::OK;
}
};
void RunServer() {
std::string server_address("0.0.0.0:50051");
GreeterServiceImpl service;
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::In
secureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wait();
}
int main(int argc, char** argv) {
RunServer();
return 0;
}
步骤 4: 实现客户端
client.cpp
:
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "service.grpc.pb.h"
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using example::Greeter;
using example::Greeting;
using example::GreetingResponse;
class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(Greeter::NewStub(channel)) {}
// 同步调用 Greeter 服务
std::string Greet(const std::string& user) {
Greeting greeting; // 创建一个请求
greeting.set_name(user);
GreetingResponse response; // 存储回应
ClientContext context;
// 实际 RPC 调用
Status status = stub_->Greet(&context, greeting, &response);
if (status.ok()) {
return response.message();
} else {
std::cerr << "RPC failed." << std::endl;
return "RPC failed";
}
}
private:
std::unique_ptr<Greeter::Stub> stub_;
};
int main(int argc, char** argv) {
GreeterClient greeter(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));
std::string user("world");
std::string reply = greeter.Greet(user); // 发起 RPC 调用
std::cout << "Greeter received: " << reply << std::endl;
return 0;
}
运行结果
- 先运行服务器:
./server
- 然后运行客户端:
./client
客户端输出应该是:
Greeter received: Hello world
这个示例中,服务器和客户端通过 gRPC 通信,客户端发送一个字符串给服务器,服务器回复一个问候语。这个简单的例子演示了 RPC 的基本概念和流程。
示例中使用的gRPC 相关的类和组件讲解
1. grpc::Server
这是 gRPC 的核心类之一,用于构建和启动 RPC 服务端。它监听来自客户端的连接,处理收到的请求,并发送响应。Server
类配合 ServerBuilder
使用,用于配置端口、安全凭证等信息。
2. grpc::ServerBuilder
这个类用于构建 grpc::Server
。它提供了一个流式接口(Fluent API),用于添加服务、配置监听端口、设置服务凭证等。一旦配置完成,可以调用 BuildAndStart()
方法来创建并启动一个 Server
实例。
3. grpc::ServerContext
这个类包含了 RPC 调用的上下文信息,例如客户端的元数据、取消状态等。它通常用于服务端方法的参数之一,以让方法能够访问和操作调用的上下文。
4. grpc::Status
Status
类用于表示 RPC 调用的结果。它包含一个状态码和一个可选的错误消息。在服务端,方法通过返回一个 Status
对象来告知客户端调用是成功还是失败。
示例中使用的example
命名空间中的类
这些类是由 Protocol Buffers(protobuf)编译器自动生成的,基于 .proto
文件中定义的服务和消息类型。
1. example::Greeter
这是一个由 protobuf 工具根据定义的 Greeter
服务自动生成的类。它包括服务端和客户端的桩(Stub)实现。
- 服务端实现:
Greeter::Service
是一个抽象基类,定义了所有在.proto
文件中指定的 RPC 方法。为了实现服务端,需要创建一个继承自Greeter::Service
的类,并实现其所有虚拟方法。 - 客户端桩:用于发起对服务的调用。
Greeter::Stub
类提供了对应于服务中每个 RPC 方法的客户端调用实现。客户端通过这些方法发起 RPC 请求,并处理响应。
2. example::Greeting
和 example::GreetingResponse
这些类是根据 .proto
文件中定义的消息格式自动生成的。Greeting
用于客户端发送请求的数据结构,而 GreetingResponse
是服务端响应的数据结构。这些类包括了对消息字段的访问方法(如 set_name()
和 message()
)以及序列化和反序列化的功能。
总结
在这个 RPC 示例中,example::Greeter
服务由 GreeterServiceImpl
类在服务器端实现,处理来自客户端的 Greet
方法调用。客户端通过 GreeterClient
类使用 Greeter::Stub
发起这些调用。使用 gRPC 框架和 protobuf 序列化机制,简化了跨网络的过程调用和数据交换的复杂性。这样,开发者可以专注于业务逻辑的实现,而无需深入了解底层网络通信的细节。