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 --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;
}
推荐学习 https://xxetb.xetslk.com/s/p5Ibb