grpc是google开源的rpc框架,默认使用protobuf序列化格式
本文介绍grpc使用的全过程,包括:
1.proto文件设计和编写
2.使用protoc编译器生成代码框架(包含接口定义)
3.根据生成的框架代码自己实现服务端和客户端的逻辑(重写框架中定义的接口)
一、proto文件
我们使用protobuf序列化格式,这个由谷歌设计的协议旨在提供高效的序列化机制,支持高性能服务
设计一个注册和登录的服务
syntax = "proto3"; // protoc版本
package IM.Login; // 命名空间: 项目名.模块名
// 定义服务类型,关键字:service
service ImLogin {
rpc Regist(IMRegistReq) returns (IMRegistRes) {} // 使用rpc关键字
rpc Login(IMLoginReq) returns (IMLoginRes) {} // 与普通函数定义类似,Login是函数名,IMLoginReq是参数,returns是返回值
}
// 定义注册类型,关键字:message
message IMRegistReq{
string user_name = 1; // 这里变量 user_name = 1 并不是赋值,1表示变量的编号,用于两端互相解析数据,下文同理
string password = 2;
}
// 注册返回
message IMRegistRes{
string user_name = 1;
uint32 user_id = 2;
uint32 result_code = 3; // 返回0的时候注册注册
}
// 登录请求
message IMLoginReq{
string user_name = 1;
string password = 2;
}
// 登录返回
message IMLoginRes{
uint32 user_id = 1;
uint32 result_code = 2;
}
二、使用protoc编译器生成框架代码
编译命令:(如何使用protoc编译,见下文)
protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=/usr/local/bin/grpc_cpp_plugin IM.Login.proto
生成C++代码IM.Login.pb.h和IM.Login.pb.cc, IM.Login.grpc.pb.h和IM.Login.grpc.pb.cc
从.proto文件生成了什么?
当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息 类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
1.对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
2.对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口 的)。
3.对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块 与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
4.对go来说,编译器会位每个消息类型生成了一个.pd.go文件。
5.对于Ruby来说,编译器会为每个消息类型生成了一个.rb文件。
6.javaNano来说,编译器输出类似域java但是没有Builder类
7.对于ObjectiveC来说,编译器会为每个消息类型生成了一个pbobjc.h文件和pbobjcm文件,.proto文件中的每一个消息有一个 对应的类。
8.对于C#来说,编译器会为每个消息类型生成了一个.cs文件,.proto文件中的每一个消息有一个对应的类。
三、根据生成的框架代码自己实现服务端和客户端的逻辑
这是根据.proto文件生成的代码目录:
我们需要根据这些文件中提供的接口定义,来实现服务端代码的逻辑,有两个步骤
1.实现接口函数
2.根据接口实现业务逻辑
服务端代码
1.实现接口函数
首先创建server.cc文件,定义头文件,开放命名空间:
#include <iostream>
#include <string>
// grpc头文件
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
// 包含我们自己proto文件生成的.h
#include "IM.Login.pb.h"
#include "IM.Login.grpc.pb.h"
// 命名空间
// grcp
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
// 自己proto文件的命名空间
using IM::Login::ImLogin;
using IM::Login::IMRegistReq;
using IM::Login::IMRegistRes;
using IM::Login::IMLoginReq;
using IM::Login::IMLoginRes;
声明一个自定义类,继承自.grpc.pb.h文件中的service类:
并实现其中的虚函数
class IMLoginServiceImpl : public ImLogin::Service {
// 注册
virtual Status Regist(ServerContext* context, const IMRegistReq* request, IMRegistRes* response) override {
std::cout << "Regist user_name: " << request->user_name() << std::endl;
response->set_user_name(request->user_name());
response->set_user_id(10);
response->set_result_code(0);
return Status::OK;
}
// 登录
virtual Status Login(ServerContext* context, const IMLoginReq* request, IMLoginRes* response) override {
std::cout << "Login user_name: " << request->user_name() << std::endl;
response->set_user_id(10);
response->set_result_code(0);
return Status::OK;
}
};
最后,实现业务逻辑,绑定ip+port,进行服务监听
void RunServer()
{
std::string server_addr("0.0.0.0:50051");
// 创建一个服务类
IMLoginServiceImpl service;
ServerBuilder builder;
builder.AddListeningPort(server_addr, grpc::InsecureServerCredentials());
builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIME_MS, 5000);
builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 10000);
builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
builder.RegisterService(&service);
//创建/启动
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_addr << std::endl;
// 进入服务循环
server->Wait();
}
int main(int argc, const char** argv)
{
RunServer();
return 0;
}
客户端代码
首先创建client.cc文件,声明头文件,开放命名空间:
#include <iostream>
#include <memory>
#include <string>
// /usr/local/include/grpcpp/grpcpp.h
#include <grpcpp/grpcpp.h>
// 包含我们自己proto文件生成的.h
#include "IM.Login.pb.h"
#include "IM.Login.grpc.pb.h"
// 命名空间
// grcp
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
// 自己proto文件的命名空间
using IM::Login::ImLogin;
using IM::Login::IMRegistReq;
using IM::Login::IMRegistRes;
using IM::Login::IMLoginReq;
using IM::Login::IMLoginRes;
实现客户端逻辑:
class ImLoginClient
{
public:
ImLoginClient(std::shared_ptr<Channel> channel)
:stub_(ImLogin::NewStub(channel)) {
}
void Regist(const std::string &user_name, const std::string &password) {
IMRegistReq request;
request.set_user_name(user_name);
request.set_password(password);
IMRegistRes response;
ClientContext context;
std::cout << "-> Regist req" << std::endl;
Status status = stub_->Regist(&context, request, &response);
if(status.ok()) {
std::cout << "user_name:" << response.user_name() << ", user_id:" << response.user_id() << std::endl;
} else {
std::cout << "user_name:" << response.user_name() << "Regist failed: " << response.result_code()<< std::endl;
}
}
void Login(const std::string &user_name, const std::string &password) {
IMLoginReq request;
request.set_user_name(user_name);
request.set_password(password);
IMLoginRes response;
ClientContext context;
std::cout << "-> Login req" << std::endl;
Status status = stub_->Login(&context, request, &response);
if(status.ok()) {
std::cout << "user_id:" << response.user_id() << " login ok" << std::endl;
} else {
std::cout << "user_name:" << request.user_name() << "Login failed: " << response.result_code()<< std::endl;
}
}
private:
std::unique_ptr<ImLogin::Stub> stub_;
};
int main()
{
// 服务器的地址
std::string server_addr = "localhost:50051";
ImLoginClient im_login_client(
grpc::CreateChannel(server_addr, grpc::InsecureChannelCredentials())
);
std::string user_name = "wjq++";
std::string password = "123456";
im_login_client.Regist(user_name, password);
im_login_client.Login(user_name, password);
return 0;
}
在Windows10上编译grpc工程,得到protoc.exe和grpc_cpp_plugin.exe
使用protoc.exe编译得到 protobuf文件
转自:在Windows10上编译grpc工程,得到protoc.exe和grpc_cpp_plugin.exe-CSDN博客
grpc是google于2015年发布的一款跨进程、跨语言、开源的RPC(远程过程调用)技术。使用C/S模式,在客户端、服务端共享一个protobuf二进制数据。在点对点通信、微服务、跨语言通信等领域应用很广,下面介绍grpc在windows10上编译,这里以编译grpc v1.42.0版本为例,进行说明,如图(1)所示:
https://github.com/grpc/grpc/tree/v1.42.0/
1
图(1) 下载grpc v1.42.0版本
1 安装编译工具
1.1 安装VS2019
下载VS2019,官网地址: https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/
个人地址: https://pan.baidu.com/s/1VaQC5_CprbTtp8mbPWCaBA
提取码:uo1b
双击该安装包,选中"使用C++的桌面开发",然后一路默认,直到安装完成。
图(2) 下载VS2019
图(3) 选中“使用C++的桌面开发”即可
1.2 安装git
git官网: ttps://git-scm.com/
git的安装方法进,参考这篇文件: git详细安装教程
1.3 安装cmake
cmake官网: https://cmake.org/download/
cmake的安装方法,参考这篇文件: cmake安装
1.4 安装nasm
nasm官网: https://www.nasm.us/
nasm的安装方法,参考这篇文件: nasm安装
1.5 安装ninja
ninja官网: https://ninja-build.org/
ninja的安装方法,参考这篇文件: ninja安装
2 配置工程
2.1 Git配置ssh
git配置ssh请参考这篇文章: https://www.cnblogs.com/yiven/p/8465054.html
2.2 修改zlib的版本号
将grpc\third_party\zlib\CMakeLists.txt里的第一行cmake版本号,改成如下:
cmake_minimum_required(VERSION 2.8...3.22)
1
2.3 下载第三方依赖包
在https://github.com/grpc/grpc/tree/v1.42.0/third_party链接里,
https://github.com/grpc/grpc/tree/v1.42.0/third_party
1
下载编译所需的第三方依赖包,具体如下:
abseil-cpp
benchmark
bloaty
boringssl
c-ares
data-plane-api
googleapis
googletest
libuv
opencensus-proto
opentelemetry-proto
protobuf
protoc-gen-validate
re2
xds
zlib
将这些第三方依赖包下载后,解压,把它们放到grpc\third_party的同名目录里。
图(4) 手动下载第三方依赖包
或者,在这个链接里下载: https://pan.baidu.com/s/1fplVSH39XML3pRnshFGnVA 提取码:dair
3 使用cmake编译grpc
打开Git ,克隆grpc工程,然后,设置第三方的依赖库,再使用make命令编译。
## 1) 克隆grpc工程
git clone --recurse-submodules -b v1.42.0 https://gitee.com/mirrors/grpc
## 2) 进入grpc工程
cd grpc
## 3) 同步第三方模块
git submodule sync
## 4)使用VS2019构建静态库
cmake -G "Visual Studio 16 2019" -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF ./CMakeLists.txt
## 5) 设置Debug目录,并编译
cmake --build . --config Debug
## 6) 设置Release目录,并编译
cmake --build . --config Release
4 生成protobuf文件
4.1 提取protoc
编译成功后,在grpc\third_party\protobuf\Release目录,会看到protoc.exe、libprotoc.lib等文件;
在grpc\Release\grpc_cpp_plugin.exe、grpc_csharp_plugin.exe,将其拷贝出来,放到D:\protobuf,如图(5)所示:
图(5) 拷贝protoc.exe、grpc_cpp_plugin.exe、grpc_csharp_plugin.exe
4.2 创建一个helloworld.proto文件
helloworld.proto的内容如下:
//helloworld.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
4.3 生成pb文件
按Win+R快捷键,输入:cmd,依次输入如下命令:
## 1)切换到D盘
D:
## 2)进入protobuf目录
D:\protobuf
## 3)执行C++方式的protoc命令
// protoc.exe -I=.\ --cpp_out=.\ --grpc_out=.\ --plugin=protoc-gen-grpc=".\grpc_cpp_plugin.exe" // helloworld.proto
D:\Test\grpc\builds\third_party\protobuf\Debug\protoc.exe --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc="grpc_cpp_plugin.exe" helloworld.proto
## 4) 执行Csharp方式的protoc命令
protoc.exe -I=.\ --csharp_out=.\ --grpc_out=.\ --plugin=protoc-gen-grpc=".\grpc_csharp_plugin.exe" helloworld.proto
本例采用C++方式的protoc命令,如图(6)所示:
protoc.exe -I=.\ --cpp_out=.\ --grpc_out=.\ --plugin=protoc-gen-grpc=".\grpc_cpp_plugin.exe" helloworld.proto
1
图(6) 按C++方式,生成pb文件
或按C# (CSharp)方式,生成pb文件,如图(7)所示:
图(7)按C#(CSharp)方式,生成pb文件
5 C++和C#版本的protoc
5.1 C++、C#共同的protoc
protoc是根据插件grpc_XXX_plugin.exe和命令 --XXX_out 参数,来生成不同语言的pb文件的。protoc.exe既可以共用,也可以单独分开。这里介绍共用的protoc,如图(8)所示表示不同的插件:
图(8) grpc_XXX_plugin.exe表示不同的语言插件
grpc通过protoc文件,进行数据传递。这里给出C++和C#版本protoc链接: https://pan.baidu.com/s/1RjUeI-89M8FkYuXDCMsMFg 提取码:s56c
对于其他语言版本的protoc,比如Java、Note.js、Ruby、Python等版本的protoc,需要按照本文第1~4节来编译grpc源码,在grpc\Release和grpc\thitd_party\protobuf\Release目录,就可以得到protoc。
5.2 C#单独的protoc
C#版本的protoc,在NuGet.org里有官方版本下载,它放在Grpc.Tools工具里,如图(9)所示:
https://www.nuget.org/packages/Grpc.Tools/2.57.0#show-readme-container
1
图(9) 下载Grpc.Tools工具,该包是C#版本的protoc包
下载Grpc.Tools工具,得到以*.nupkg结尾的压缩包,这个*.nupkg后缀名改成*.rar,如图(10)所示:
图(10) 下载Grpc.Tools,并修改后缀名
然后将这rar文件解压,在grpc.tools\tools\windows_x64目录下,即可看到protoc.exe、gprc_csharp_plugin.exe文件,这2个文件用于生成C#版本grpc对应的pb文件,如图(11)所示:
图(11) 在grpc.tools\tools\windows_x64里