1.RPC(Remote Procedure Call)
RPC,即远程过程调用,允许位于不同地址空间的程序之间进行通信,就像调用本地服务一样简单。RPC的核心思想是通过网络将程序调用请求发送到远程服务器,服务器执行请求后返回结果。
1. rpc完整流程(函数映射/编码解码/网络传输)
RPC(远程过程调用)的完整流程可以分为以下几个关键步骤,这些步骤在客户端和服务端之间进行交互,以实现远程方法调用。以下是详细的流程描述:
1. 定义服务接口
-
服务定义:在RPC框架中,首先需要定义服务接口,包括可以远程调用的方法及其参数和返回类型。这通常通过一个接口定义语言(IDL)来完成,例如gRPC使用
.proto
文件。 -
示例:
service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
2. 生成代码
-
代码生成:使用框架提供的工具(如
protoc
编译器)将服务定义文件转换为特定语言的代码。生成的代码包括客户端存根(stub)和服务端骨架(skeleton)。 -
客户端存根:客户端存根提供了与服务端相同的方法接口,但实际调用时会通过网络发送请求。
-
服务端骨架:服务端骨架定义了服务端需要实现的方法接口。
3. 服务端实现
-
实现服务接口:服务端开发者根据生成的骨架代码实现服务接口,定义每个方法的具体逻辑。
-
启动服务:服务端启动一个RPC服务器,监听客户端的连接请求。服务端需要注册服务接口,并绑定到特定的端口。
-
示例(gRPC C++):
class GreeterServiceImpl : public Greeter::Service { Status SayHello(ServerContext* context, const HelloRequest* request, HelloReply* reply) override { std::string prefix("Hello "); reply->set_message(prefix + request->name()); return Status::OK; } }; int main() { GreeterServiceImpl service; ServerBuilder builder; builder.AddListeningPort("localhost:50051", grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr<Server> server(builder.BuildAndStart()); server->Wait(); return 0; }
4. 客户端调用
-
创建客户端存根:客户端使用生成的存根代码创建一个客户端存根实例,该存根用于调用远程服务。
-
发起请求:客户端通过存根调用远程方法,将请求参数序列化后发送到服务端。
-
示例(gRPC C++):
int main() { auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()); auto stub = Greeter::NewStub(channel); HelloRequest request; request.set_name("world"); HelloReply reply; grpc::ClientContext context; Status status = stub->SayHello(&context, request, &reply); if (status.ok()) { std::cout << "Greeter received: " << reply.message() << std::endl; } else { std::cerr << "RPC failed" << std::endl; } return 0; }
5. 网络传输
-
请求传输:客户端将请求数据序列化后通过网络发送到服务端。
-
响应传输:服务端处理请求后,将响应数据序列化并发送回客户端。
6. 服务端处理
-
接收请求:服务端接收到客户端的请求后,反序列化请求数据。
-
执行方法:服务端调用本地实现的方法,处理请求逻辑。
-
返回响应:服务端将响应数据序列化后发送回客户端。
7. 客户端接收响应
-
反序列化响应:客户端接收到响应数据后,反序列化响应内容。
-
处理结果:客户端根据反序列化的响应数据处理结果。
8. 关闭连接
-
客户端关闭:客户端完成调用后,关闭与服务端的连接。
-
服务端关闭:服务端在完成所有请求处理后,可以选择关闭服务。
2.rpc与http1.1区别
1. 数据格式
-
HTTP:
-
HTTP 数据格式通常是文本格式,如 HTML、JSON、XML 等。
-
HTTP 头部和正文是明文的,易于理解和调试。
-
-
RPC:
-
RPC 数据格式可以是二进制格式(如 Protocol Buffers、Thrift)或文本格式(如 JSON、XML)。
-
二进制格式的 RPC 数据更紧凑,传输效率更高,但调试时不如文本格式直观。
-
2. 连接管理
-
HTTP:
-
HTTP/1.1 支持持久连接(keep-alive),但每个请求仍然需要单独建立和关闭连接。
-
-
RPC:
-
RPC 框架通常会优化连接管理,以减少连接开销。例如,gRPC 使用 HTTP/2 的多路复用特性,允许在同一个连接上并发处理多个 RPC 调用。
-
一些 RPC 框架(如 Thrift)可以使用长连接(long-lived connections),减少连接建立和关闭的开销。
-
2.gRPC(Google Remote Procedure Call)
gRPC是由Google开发的高性能、开源、通用的RPC框架,基于HTTP/2协议标准设计开发,默认采用Protocol Buffers(ProtoBuf)作为数据序列化协议。gRPC允许客户端应用直接调用其他机器上的服务器应用中的方法,如同调用本地对象一样。
1.HTTP/2 主要特点
-
二进制帧结构:HTTP/2 将数据以二进制帧的形式传输,而不是 HTTP/1.x 的文本格式。这种二进制帧结构使得协议更高效,减少了数据传输量。
-
多路复用:HTTP/2 允许在一个 TCP 连接上同时发送多个请求和响应,避免了 HTTP/1.x 中的“队头阻塞”问题。这种多路复用机制大大提高了网络资源的利用率。
-
头部压缩:HTTP/2 使用 HPACK 算法对 HTTP 头部进行压缩,显著减少了头部数据的传输量。这不仅提高了传输效率,还减少了带宽占用。
-
服务器推送:HTTP/2 支持服务器主动向客户端推送资源,而无需客户端明确请求。这一特性可以优化页面加载速度,提升用户体验。
-
流控制:HTTP/2 引入了流的概念,允许对每个流进行优先级设置和流量控制。这使得资源可以根据重要性优先传输,进一步优化了性能。
3.grpc使用
1.定义服务和消息
syntax = "proto3";
package helloworld;
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
service
是一个关键字,用于在 .proto
文件中定义一个服务接口。服务接口是一组远程过程调用(RPC)方法的集合,客户端可以通过这些方法与服务端进行通信。
1. rpc
关键字
rpc
是 .proto
文件中用于定义远程过程调用的关键字。它表示这是一个可以被客户端调用的服务端方法。
2. 方法名:SayHello
SayHello
是这个 RPC 方法的名称。客户端可以通过这个名称调用服务端的对应方法。
3. 请求消息类型:HelloRequest
HelloRequest
是客户端发送给服务端的请求消息类型。它定义了客户端需要提供的数据结构。例如:
message HelloRequest {
string name = 1;
}
在这个例子中,HelloRequest
包含一个 string
类型的字段 name
,客户端需要在调用 SayHello
方法时提供这个字段的值。
4. 返回消息类型:HelloReply
HelloReply
是服务端返回给客户端的响应消息类型。它定义了服务端需要返回的数据结构。例如:
message HelloReply {
string message = 1;
}
在这个例子中,HelloReply
包含一个 string
类型的字段 message
,服务端会在处理完请求后将结果放在这个字段中返回给客户端。
5. 方法体:{}
在 .proto
文件中,方法体是空的 {}
。实际的实现逻辑需要在生成的代码中完成
2. 服务端骨架
服务端骨架是基于 .proto
文件生成的代码,用于定义服务接口和处理 RPC 请求。
2.1 服务端骨架代码结构
生成的 greeter.grpc.pb.h
文件中定义了服务接口 Greeter
:
namespace helloworld {
class Greeter {
public:
virtual ~Greeter() {}
virtual ::grpc::Status SayHello(
::grpc::ServerContext* context,
const ::helloworld::HelloRequest* request,
::helloworld::HelloReply* response) = 0;
};
}
1.Status
的作用
::grpc::Status
是 gRPC 的一个类,用于表示 RPC 调用的结果状态。它包含以下信息:
-
状态码(
code
):表示 RPC 调用是否成功,常见的状态码有:-
OK
(0):表示调用成功。 -
CANCELLED
(1):表示调用被取消。 -
UNKNOWN
(2):表示未知错误。 -
INVALID_ARGUMENT
(3):表示请求参数无效。 -
DEADLINE_EXCEEDED
(4):表示超时。 -
等等,更多状态码可以参考 gRPC Status Codes。
-
-
错误信息(
error_message
):如果调用失败,可以提供更详细的错误信息。 -
错误详情(
error_details
):可以提供更复杂的错误详情,通常是一个序列化的google.protobuf.Any
消息。
2.::grpc::ServerContext* context:
提供 RPC 调用的上下文信息,如超时、取消、元数据等。
-
身份验证和授权:
ServerContext
可以用于处理与身份验证和授权相关的操作,例如获取客户端的身份信息。 -
超时和取消:它允许设置超时时间,或者在 RPC 调用过程中取消操作。
-
元数据:可以用于处理请求和响应的元数据(如 HTTP 头信息),例如在跨域请求中传递认证信息
2.2 实现服务端逻辑
继承 Greeter::Service
类并实现 SayHello
方法:
#include "greeter.grpc.pb.h"
class GreeterServiceImpl final : public helloworld::Greeter::Service {
grpc::Status SayHello(grpc::ServerContext* context,
const helloworld::HelloRequest* request,
helloworld::HelloReply* reply) override {
std::string prefix("Hello ");
reply->set_message(prefix + request->name());
return grpc::Status::OK;
}
};
final
是一个类修饰符,用于表示一个类不能被继承,不能再被派生出子类。
2.3 启动 gRPC 服务器
创建并启动服务器:
#include <grpcpp/grpcpp.h>
#include "greeter.grpc.pb.h"
void RunServer() {
std::string server_address("0.0.0.0:50051");
GreeterServiceImpl service;
grpc::ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wait();
}
int main(int argc, char** argv) {
RunServer();
return 0;
}
gRPC 的 C++ 实现中,grpc::ServerBuilder
是一个用于构建和配置 gRPC 服务器的工具类。它提供了一系列方法来设置服务器的各种参数,包括监听端口、注册服务、设置证书等。
builder.AddListeningPort
方法用于添加服务器监听的端口。
grpc::ServerBuilder& AddListeningPort(
const std::string& addr,
std::shared_ptr<grpc::ServerCredentials> creds,
int* selected_port = nullptr);
builder.RegisterService
用于注册一个服务。它的原型如下:
grpc::ServerBuilder& RegisterService(grpc::Service* service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
建并启动一个 gRPC 服务器实例,并将其存储在一个 std::unique_ptr
中
server->Wait()
:阻塞主线程,直到服务器停止。这通常是一个无限循环,直到服务器被显式停止(例如,通过信号处理或其他机制)
3. 客户端存根
客户端存根是基于 .proto
文件生成的代码,用于简化客户端对服务端的 RPC 调用。
3.1 客户端存根代码结构
生成的 greeter.grpc.pb.h
文件中定义了客户端存根类 Greeter::Stub
:
namespace helloworld {
class Greeter::Stub {
public:
explicit Stub(grpc::ChannelInterface* channel);//构造函数声明,初始化一个客户端存根
grpc::Status SayHello(
grpc::ClientContext* context,
const HelloRequest& request,
HelloReply* response);
};
}
3.2 使用客户端存根调用服务
创建客户端并调用服务端的方法:
#include <grpcpp/grpcpp.h>
#include "greeter.grpc.pb.h"
class GreeterClient {
public:
//构造函数的作用是初始化 GreeterClient 类的成员变量 stub_,并将其绑定到一个 gRPC 通道(channel)上
GreeterClient(std::shared_ptr<grpc::Channel> channel)
: stub_(helloworld::Greeter::NewStub(channel)) {}
std::string SayHello(const std::string& user) {
helloworld::HelloRequest request;
request.set_name(user);
helloworld::HelloReply reply;
grpc::ClientContext context;
grpc::Status status = stub_->SayHello(&context, request, &reply);
if (status.ok()) {
return reply.message();
} else {
return "RPC failed";
}
}
private:
std::unique_ptr<helloworld::Greeter::Stub> stub_;
};
int main(int argc, char** argv) {
GreeterClient client(grpc::CreateChannel(
"localhost:50051", grpc::InsecureChannelCredentials()));
std::string user("world");
std::string reply = client.SayHello(user);
std::cout << "Greeter received: " << reply << std::endl;
return 0;
}
auto channel = grpc::CreateChannel("192.168.1.100:50051", grpc::InsecureChannelCredentials());
1.GreeterClient
与 Stub
的关系
-
封装关系:
GreeterClient
封装了Stub
,使得客户端代码更加简洁和易于使用。 -
调用关系:
GreeterClient
通过Stub
来发起 RPC 调用。例如,在SayHello
方法中,GreeterClient
使用stub_
调用了SayHello
方法。 -
客户端类(如
GreeterClient
)封装了Stub
:在初始化时,客户端类通过Stub
的工厂方法创建了一个Stub
对象,并将其存储在成员变量中。 -
客户端类通过
Stub
发起 RPC 调用:客户端类的方法(如SayHello
)通过Stub
的方法向服务端发起 RPC 请求。
存根(Stub) 是一个客户端代理对象,用于代理对服务端的远程调用。通过在客户端类中初始化存根,你可以方便地调用存根中的方法,从而实现与服务端的通信。
2.helloworld::Greeter::NewStub:
这是一个静态方法,用于创建一个helloworld::Greeter::Stub
对象。它接受一个 std::shared_ptr<grpc::Channel>
对象作为参数,并返回一个std::unique_ptr<helloworld::Greeter::Stub>
对象。
RpcClient::RpcClient(const std::string& server_address) {
auto channel =
grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials());
stub_ptr_ = monitor::proto::GrpcManager::NewStub(channel);
}
1. CreateChannel 的作用
CreateChannel
用于创建一个到 gRPC 服务器的连接通道。这个通道是客户端与服务器之间通信的基础,通过它,客户端可以发送请求并接收响应。
2. CreateChannel 的参数
通常,CreateChannel
需要以下参数:
-
服务器地址:指定 gRPC 服务器的地址,通常是
host:port
格式。 -
通道选项(可选):可以设置一些通道的配置,例如超时时间、证书等。
3.ClientContext
的作用
-
设置超时时间:防止 RPC 调用无限等待。
-
添加元数据:可以传递额外的信息,比如身份认证、跟踪 ID。
-
管理流控制:在流式 RPC 中,可以取消请求或调整流行为。
-
获取 RPC 状态:检查调用是否成功或失败。
4. 编译和运行
4.1 编写 CMakeLists.txt
find_package(protobuf CONFIG REQUIRED)
find_package(gRPC CONFIG REQUIRED)
find_package(Threads REQUIRED)
find_package(c-ares CONFIG)
find_package(Threads)
set(PROTO_FILES
monitor_info.proto
cpu_load.proto
cpu_softirq.proto
cpu_stat.proto
mem_info.proto
net_info.proto
)
add_library(monitor_proto ${PROTO_FILES})
target_link_libraries(monitor_proto
PUBLIC
protobuf::libprotobuf
gRPC::grpc
gRPC::grpc++
)
target_include_directories(monitor_proto PUBLIC
${PROTOBUF_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR})
get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
protobuf_generate(TARGET monitor_proto LANGUAGE cpp)
protobuf_generate(TARGET monitor_proto LANGUAGE grpc GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc PLUGIN "protoc-gen-grpc=${grpc_cpp_plugin_location}")
find_package(Protobuf REQUIRED)
find_package(gRPC CONFIG REQUIRED)
find_package(Threads REQUIRED)
find_package(c-ares CONFIG)
set(
SOURCES
main.cpp
rpc_server.cpp
)
add_executable(server ${SOURCES})
set(CMAKE_BUILD_TYPE Debug)
target_include_directories(server PUBLIC "/home/book/Desktop/proj/work/build/proto")
target_link_directories(server PUBLIC "/home/book/Desktop/proj/work/build/proto")
target_link_libraries(server
PUBLIC
monitor_proto
protobuf::libprotobuf
gRPC::grpc
gRPC::grpc++
)
4.2 编译和运行
mkdir build
cd build
cmake ..
make
# 启动服务端
./greeter_server
# 启动客户端
./greeter_client