gRPC 基础编码使用手册

简介: gRPC 基础编码使用手册

前言:针对grpc 目前在C++ 开发过程中存在初学难懂,使用没有成体系说明的问题,这里对c++下的grpc 如何在代码层面使用进行简单说明。希望能够帮到更多人。

接口说明

本文后续使用代码均基于 下述的 proto文件,文件名为TestProto.proto 包含常见的使用示例

序列化规则文件

syntax = "proto3";

package TestProto;
// 这个服务类是请求响应的模式即单向流
service SvrPage{
  // 数据传输监控
  rpc GetTransferState(GetTransferStateRequest) returns (GetTransferStateResponse) {}
}

// 数据传输监控请求消息
message GetTransferStateRequest {
  string dataType = 1; // 数据类型
  string time = 2; // 数据日期(YYYY-MM-DD)
}

// 数据传输监控响应消息
message GetTransferStateResponse {
  int32 code = 1; // 响应码
  string msg = 2; // 响应消息
  // 结构体数组
  repeated GetTransferStateDataInfo data = 3; // 数据列表
}
// 数据信息
message GetTransferStateDataInfo {
// 基本类型数组
  repeated int32 data = 1; // 数据记录
  RecordMap mapData = 2; // 字典数据
}
//字段值
message FieldValueQc{
    string code = 1; //编码
    float value = 2; //数据值
    int32 qcmark = 3; //质控码
    bool lack = 4;//是否缺测
}

message RecordMap{
// 字典数据
  map<string, FieldValueQc> line = 1; //key为code
}

// 监控服务 (这个服务是双向流,即异步发送请求应答)
service MonitorCenter {
    rpc MonitorUpgradeState(stream UpgradeStateRequest) returns (stream UpgradeStateResponse);
}
message UpgradeStateRequest {}
message UpgradeStateResponse {
  int32 code = 1;
  string msg = 2;
  UpgradeData data = 3;
}
message UpgradeData {
  string state = 1;
  repeated LogEntry lastedLog = 2;
}
message LogEntry {
  string content = 1;
  string time = 2;
}

服务端代码

服务端分两步:

  1. 初始化:
  • 创建链接
  • 挂起等待调用

 2.远程调用实现

  • 实现具体代码逻辑

也就是说:

  1. 如果你需要在服务端实现其他的初始化内容,请在挂起等待调用前
  2. 如果你需要在远程调用中使用其他资源,请确保资源可用(公有资源,非抢占,过程中不存在挂起等待情况)

初始化

....
// 这里只对下面初始化需要使用的部分作说明,完整说明在远程调用实现部分
class CSvrPageImpl final : public SvrPage::Service
{
    .....
};
// =============================================
// = 本端作为服务端 调用的服务区间 定义开始
// =============================================
/**
 * @brief gRPC 作为服务端运行时的主体入口
 *
 */
void RunServer()
{
    // 这里实现指定绑定接口部分,接受string类型的对象传入参数
    std::string server_address("0.0.0.0:50051");
    // 这里初始化的类对象实例是在头文件中继承了`proto`文件中对应服务server 名称的类
    CSvrPageImpl service;
    // 固定 svr 创建方法
    ServerBuilder builder;
    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
    builder.RegisterService(&service);
    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on " << server_address << std::endl;
    // 至此,此进程进入挂起等待信息接入。
    server->Wait();
}

int main ()
{
    RunServer();
    return 0;
}

远程调用实现

这里着重讲述如何实现具体的远程调用逻辑不涉及如何将程序运行起来,运行部分请参考上面的初始化部分。

#ifndef _OP_TEST_HPP_
#define _OP_TEST_HPP_
// 这里引用的头文件是在安装完成grpc服务后自动安装好的
#include <grpcpp/grpcpp.h>
// 这里的头文件是通过protobuffer程序生成的proto文件对应源码库
#include "../../include/proto/TestProto.grpc.pb.h"
#include "../../include/proto/TestProto.pb.h"

// 这里是服务端对应的必须内容
using grpc::Server;
using grpc::Status;
using grpc::ServerBuilder;
using grpc::ServerContext;

// 这里是根据proto文件定义的自己的message服务类
// 规则是:文件名:message
using TestProto::GetTransferStateRequest;
using TestProto::GetTransferStateResponse;
using TestProto::SvrPage;

using TestProto::GetTransferStateDataInfo;

using TestProto::RecordMap;

// 服务端类继承重载方法
class CSvrPageImpl final : public SvrPage::Service
{
    // 接口定义,可以将定义声明分离,不必一定写成内嵌函数
    // 返回值是grpc提供的自定义的状态类型,支持多种返回内容
    grpc::Status GetTransferState(ServerContext *context, const GetTransferStateRequest *request, GetTransferStateResponse *response) override
    {
        // 实现获取传入的逻辑
        std::string strDatatype = request->datatype();
        std::string strtime = request->time();

        std::cout << __LINE__ << __TIME__ << std::endl;
        std::cout << "strDatatype: " << strDatatype << std::endl;
        std::cout << "strtime: " << strtime << std::endl;

        // 拼接返回值
        response->set_code(200);
        response->set_msg("success");
        GetTransferStateDataInfo *sData = response->add_data();

        sData->add_data(100);   // 单项插入 数组尾
        sData->add_data(101);   // 单项插入 数组尾

        sData->set_data(3,102); // 指定修改 数组 第三位的数据 为102

        RecordMap *sRecordMap = sData->mutable_mapdata(); // 返回结构体对象指针,如果没有则创建

        return grpc::Status::OK;
    }
};
#endif // _OP_TEST_HPP_

客户端代码

客户端分三步是:


  1. 初始化
  2. 创建一个 gPRC 通道
  3. 调用接口


注:grpc支持返回原生的grpc状态码,作为远程调用的执行结果。 这是和grpc接口的返回值不同的概念。以此可以得到接口服务的调用结果和返回内容两项,从而实现更加宽泛的业务需求。

初始化

注意:为了保证最小依赖,相关的模版容器和字符串以及线程和其他引用的声明并未列出

#ifndef _OP_TEST_HPP_
#define _OP_TEST_HPP_
// 这里引用的头文件是在安装完成grpc服务后自动安装好的
#include <grpcpp/grpcpp.h>
// 这里的头文件是通过protobuffer程序生成的proto文件对应源码库
#include "../../include/proto/TestProto.grpc.pb.h"
#include "../../include/proto/TestProto.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;

class CClientPageImpl
{
public:
    CClientPageImpl(std::shared_ptr<Channel> channel)
        : stub_(TestProto::NewStub(channel)) {}
    // 调用远程函数
    Status GetTransferState(const GetTransferStateRequest &request, GetTransferStateResponse &response);
private:
    std::unique_ptr<DeviceService::Stub> stub_; // 通道

}
#endif // _OP_TEST_HPP_

创建

接口接收一个string类型对象作为绑定地址,另外这里主要对创建方式进行介绍,相关的前置依赖和类对象类型请参见初始化。

Status CClientPageImpl::GetTransferState(const std::string &strDatatype, const std::string &strtime)

int main()
{
 // 创建一个gRPC通道
    std::shared_ptr<Channel> channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials());
    CClientPageImpl client(channel);
....
    return 0;
}

调用接口

std::string CClientPageImpl::GetTransferState(const GetTransferStateRequest &request, GetTransferStateResponse &response)
{
    ClientContext context;
    Status status = stub_->SendRTRecordData(&context, request, &response);
    // 对它的状态进行操作。
    if (status.ok()) {
      return std::to_string(response.data_size());
    } else {
      std::cout << __FILE__ << __LINE__ << std::endl;
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC 失败";
    }
]

int main()
{
    // 创建一个gRPC通道
    std::shared_ptr<Channel> channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials());
    CClientPageImpl client(channel);
    ClientContext context;
    GetTransferStateRequest request;
    // 初始化传入接口
    GetTransferStateResponse response;
    std::string strRet = context.GetTransferState(request,response);
    return 0;
}

序列化参数结构体读取 写入方式汇总

注意:grpc会对变量名称做优化,在使用Linux命名法、驼峰(大小驼峰)命名法时可能会出现相关函数和变量名称有些许差异的情况,请注意识别。

优化变量名排查方法:在grpc生成的对应源码文件中搜索 消息的类型定义名称,会在各个属性的类定义前有一行注释表明当前类是针对哪一个属性的。

读取方式

常量

常量是指proto文件中定义的对象类型是 数字、字符、这些基本类型

读取时,对应的结构体类中提供了同名的函数,返回值是对象对应值。

注意:grpc会对变量名称做优化,在使用Linux命名法、驼峰(大小驼峰)命名法时可能会出现相关函数和变量名称有些许差异的情况,请注意识别。

优化变量名排查方法:在grpc生成的对应源码文件中搜索 消息的类型定义名称,会在各个属性的类定义前有一行注释表明当前类是针对哪一个属性的。


结构体

结构体是指proto文件中定义的对象类型是嵌套结构体。即Struct 中套一个 struct。

读取时,对应的消息类中提供了同名的函数,返回值是结构体对象指针。

数组

数组是指proto文件中定义的对象类型加了一个repeated关键字 。常见于结构体数组。repeated int32 data = 1;

读取时,对应的消息类中提供了同名的函数,返回值是结构体对象的引用。

字典

字典是指proto文件中定义的对象类型 关键字为map。常见于复杂结构。

读取时,对应的消息类中提供了同名的函数,返回值是字典对象的引用。


写入方式

由于上述内容已经对相关类型做了介绍,下面将主要以代码的方式进行说明,不再冗余介绍


  1. 常量
    set_变量名() ;
  2. 结构体
    mutable_结构体变量名();
  3. 数组
    add_数组变量名();
  4. 字典
    auto *p = mutable_字典名();
    (*p) [键值] = value ;

编译常见问题

同名 变量冲突

  1. 结构体变量名和服务名
  2. 结构体变量名和命名空间名
  3. 服务名和命名空间名

多三方库同名冲突

  1. 避免connect,redis,sql,这些常见场景中的同名冲突,建议增加特征字符。


分享一个有趣的 学习链接:https://xxetb.xet.tech/s/HY8za


目录
相关文章
|
8月前
|
Cloud Native Go 数据处理
你可能还不知道 golang 的高效编码细节
你可能还不知道 golang 的高效编码细节
|
10天前
|
网络协议 前端开发 JavaScript
开源项目SMSS发开指南(三)——protobuf协议设计
开源项目SMSS发开指南(三)——protobuf协议设计
|
2月前
|
网络协议 Go 数据安全/隐私保护
golang开源的可嵌入应用程序高性能的MQTT服务
golang开源的可嵌入应用程序高性能的MQTT服务
330 2
|
1月前
|
缓存 网络协议 安全
【常见开源库的二次开发】HTTP之libcurl库——基础知识扫盲(一)
【常见开源库的二次开发】HTTP之libcurl库——基础知识扫盲(一)
28 1
|
15天前
|
算法 网络协议 安全
一个关于proto 文件的经验分享 :gRPC 跨语言双端通信显示错误码:12 UNIMPLEMENTED (附赠gRPC错误码表)
一个关于proto 文件的经验分享 :gRPC 跨语言双端通信显示错误码:12 UNIMPLEMENTED (附赠gRPC错误码表)
16 0
|
9月前
|
编解码 Rust 自然语言处理
gRPC源码分析(三):从Github文档了解gRPC的项目细节
从这里可以看出,gRPC虽然是支持多语言,但原生的实现并不多。如果想在一些小众语言里引入gRPC,还是有很大风险的,有兴趣的可以搜索下TiDB在探索rust的gRPC的经验分享。
128 1
|
Go 流计算
gRPC阅读日记(七)客户端的RPC构建2
gRPC阅读日记(七)客户端的RPC构建2
|
JSON 关系型数据库 MySQL
接口自动化 基于python实现的http+json协议接口自动化测试框架源码(实用改进版)
接口自动化 基于python实现的http+json协议接口自动化测试框架源码(实用改进版)
121 0
|
编解码 分布式计算 Java
基于 netty 封装的超简单通俗易用 服务端客户端交互框架 《net-framework》原理,源码和使用说明,开箱即用,只需要开发业务逻辑,完全自定义无限扩充 [结尾附github源码]
基于 netty 封装的超简单通俗易用 服务端客户端交互框架 《net-framework》原理,源码和使用说明,开箱即用,只需要开发业务逻辑,完全自定义无限扩充 [结尾附github源码]
基于 netty 封装的超简单通俗易用 服务端客户端交互框架 《net-framework》原理,源码和使用说明,开箱即用,只需要开发业务逻辑,完全自定义无限扩充 [结尾附github源码]
|
XML 存储 JSON
IM通讯协议专题学习(七):手把手教你如何在NodeJS中从零使用Protobuf
现在随着WebSocket协议的越来越成熟,浏览器支持的越来越好,Web端的即时通讯应用也逐渐拥有了真正的“实时”能力,相关的技术和应用也是层出不穷,而Protobuf也同样可以用在WebSocket的通信中。而且目前比较活跃的WebSocket开源方案中,都是用NodeJS实现的,比如:socket.io和sockjs都是如此,因而本文介绍Protobuf在NodeJS上的使用,也恰是时候。
285 0
IM通讯协议专题学习(七):手把手教你如何在NodeJS中从零使用Protobuf