在这里插入图片描述

Grpc服务器

在这里插入图片描述
gRPC是Google开发的一种高性能、开源的远程过程调用(RPC)框架。它可以让客户端应用程序像调用本地服务一样轻松地调用远程服务,并提供了多种语言的支持,如C++、Java、Python、Go等。
gRPC使用Protocol Buffers作为数据格式,可以在不同的平台上进行应用程序之间的通信,支持多种编程语言和多种操作系统。它采用基于HTTP/2的协议,提供了高效、快速且可扩展的远程调用功能,并带有负载均衡、认证、监控等功能,方便用户管理和维护分布式系统。
gRPC 使用Protocol Buffers(protobuf)作为接口定义语言(IDL)来定义消息和服务。protobuf 是 Google 开发的一个语言中立、平台中立的序列化数据格式。
**Protocol Buffers (Protobuf)**:协议缓冲区 (Protobuf):
Protocol Buffers 是一种轻量级的、语言中立、平台中立的序列化结构化数据的方法。

  • 序列化是将数据结构(如对象)转换为字节流的过程,使其可以存储或通过网络传输。
  • 反序列化是从字节流中恢复原始数据结构的过程。

proto文件编写

在项目的根目录下创建一个proto名字为message.proto

定义了一个 message.proto 文件,它描述了服务(VarifyService)和消息(GetVarifyReq、GetVarifyRsp)。该文件是服务接口和消息格式的“合同”

syntax = "proto3";//表示使用的是 Protocol Buffers 第3版

package message;//声明这个 proto 文件的包名为

service VarifyService {
rpc GetVarifyCode (GetVarifyReq) returns (GetVarifyRsp) {}
}

message GetVarifyReq {
string email = 1;
}

message GetVarifyRsp {
int32 error = 1;
string email = 2;
string code = 3;
}

service VarifyService { rpc GetVarifyCode (GetVarifyReq) returns (GetVarifyRsp) {} }

  • 定义了一个叫做 VarifyService 的服务;
  • 这个服务有一个远程方法 GetVarifyCode,参数是 GetVarifyReq,返回是 GetVarifyRsp;
  • 这个服务接口可以用于 gRPC 框架生成 服务端 和 客户端 的接口代码,进行远程调用。

Client

构造 GetVarifyReq(email="abc@example.com")

stub.GetVarifyCode(req) ——[gRPC底层自动发网络请求]→

Server 解析请求 → 执行逻辑 → 返回 GetVarifyRsp

Client 收到响应(error=0, code=”8391”)

Protobuf 是通过字段编号(如 1, 2, 3)来传输和识别数据的字段,不传字段名,所以相比 JSON 更高效。

在当前GateServer项目下用终端打开,利用grpc进行编译

F:\04Code\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe  -I="." --grpc_out="." --plugin=protoc-gen-grpc="F:\04Code\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exe" "message.proto"

编译后生成grpc.pb.h和grpc.pb.cc文件,保存了用grpc通信的接口,但是通信接口所用的参数需要额外生成;
在这里插入图片描述

F:\04Code\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exe --cpp_out=. "message.proto"

在这里插入图片描述

Grpc客户端

这段代码实现了一个 gRPC 客户端 和一个 HTTP 请求处理逻辑,主要功能是通过 HTTP 接收验证码请求,并通过 gRPC 客户端调用远程服务获取验证码。

VarifyGrpcClient

#include <grpcpp/grpcpp.h>
#include "message.grpc.pb.h"
#include "const.h"
#include "Singleton.h"
using grpc::Channel;
using grpc::Status;
using grpc::ClientContext;

using message::GetVarifyReq;
using message::GetVarifyRsp;
using message::VarifyService;

class VerifyGrpcClient:public Singleton<VerifyGrpcClient>
{
friend class Singleton<VerifyGrpcClient>;
public:
//客户端接口函数,用于向远程 gRPC 服务请求验证码。
GetVarifyRsp GetVarifyCode(std::string email) {
ClientContext context;
GetVarifyRsp reply;
GetVarifyReq request;
request.set_email(email);
//通过客户端存根 (stub_) 调用远程服务的 GetVarifyCode 方法。
Status status = stub_->GetVarifyCode(&context, request, &reply);

if (status.ok()) {

return reply;
}
else {
reply.set_error(ErrorCodes::RPCFailed);
return reply;
}
}

private:
VerifyGrpcClient() {
std::shared_ptr<Channel> channel = grpc::CreateChannel("127.0.0.1:50051", grpc::InsecureChannelCredentials());
stub_ = VarifyService::NewStub(channel);
}

std::unique_ptr<VarifyService::Stub> stub_;
};

VerifyGrpcClient() 构造函数

通过 grpc::CreateChannel() 创建一个 gRPC 通道,连接到本地的 127.0.0.1:50051 地址。这是你 gRPC 服务器的 IP 地址和端口。

使用 grpc::InsecureChannelCredentials() 创建一个不加密的通道(适用于本地测试)。在生产环境中,你可能使用加密通道。

通过 VarifyService::NewStub(channel) 创建一个 VarifyService 的客户端存根(stub),它是与 gRPC 服务进行通信的接口。
post请求获取验证码的逻辑里添加处理
VarifyService::Stub

在 gRPC 中,Stub 是一个客户端接口类,它定义了如何调用远程服务的所有方法。它负责将客户端请求封装成远程调用(RPC)并将结果返回给客户端。

  • Stub 类是由 protoc 编译器根据 .proto 文件自动生成的。
  • Stub 类中的方法直接对应于在 .proto 文件中定义的 RPC 方法。这些方法会通过网络与远服务器进行通信,发送请求并接收响应。
  • 每个 Stub 都会为每个 RPC 方法提供一个对应的函数,例如,在你的例子中,VarifyService::Stub 会有一个 GetVarifyCode 函数。

VarifyService::NewStub
VarifyService::NewStub 是一个 工厂方法,用于创建一个 Stub 对象,它将负责客户端与 gRPC 服务端的通信。你可以通过这个方法来初始化一个 Stub 实例,从而与服务端进行 RPC 调用。

RegPost用于注册一个 POST 请求的回调函数,当客户端发起请求时,这个回调函数就会执行。

RegPost("/get_varifycode", [](std::shared_ptr<HttpConnection> connection) {
// 用来将 HTTP 请求体的数据转换成字符串。
auto body_str = boost::beast::buffers_to_string(connection->_request.body().data());
std::cout << "receive body is " << body_str << std::endl;
connection->_response.set(http::field::content_type, "text/json");
Json::Value root;
Json::Reader reader;
Json::Value src_root;
bool parse_success = reader.parse(body_str, src_root);
if (!parse_success) {
std::cout << "Failed to parse JSON data!" << std::endl;
root["error"] = ErrorCodes::Error_Json;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}

auto email = src_root["email"].asString();
//调用 VerifyGrpcClient 的 GetVarifyCode 方法,通过 gRPC 获取验证码。
GetVarifyRsp rsp = VerifyGrpcClient::GetInstance()->GetVarifyCode(email);
cout << "email is " << email << endl;
root["error"] = rsp.error();
root["email"] = src_root["email"];
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
});

整个过程客户端通过 HTTP 请求发送 email,服务器调用 gRPC 客户端通过 email 请求验证码,最后将响应返回给客户端。

nodejs验证服务

在这里插入图片描述
grpc服务器启动,邮件成功发送
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

验证码的过期失效通过redis服务来设置

gateserver实际上是通过boost实现的网络库,提供http的服务

关于此节的思考:

1.“为什么实现一个“获取验证码”的功能不用 HTTP/REST 接口,用 gRPC 实现?有什么优势?”或者说为什么要用grpc通信
在这里插入图片描述

统一接口定义指的是:你把服务接口(函数名、参数、返回值)都写在一个 .proto 文件里,由它作为唯一标准,然后用它自动生成服务端/客户端代码,避免手写出错。
模块间 RPC 调用,指的是你的系统被拆成多个服务模块,它们之间通过“远程调用函数”来通信,而不是直接调用本地函数。

2.为什么VerifyGrpcClient要用单例模式

单例是一种“全局唯一客户端访问入口”的设计模式,它让你只创建一个 gRPC 客户端对象,保证连接和资源的复用,还防止你不小心创建多个对象、建立多个连接,破坏性能和逻辑一致性。

虽然 stub_ 是全局唯一的共享实例,但它是线程安全的、支持并发的,不同模块可以同时调用它,不会互相影响。你只要保证每次调用用的参数是独立的,就完全没问题。