已经折腾grpc几天了,也基本搞明白了怎么用,这里做一个简单的记录,以便日后需要的时候有个参考。

按照顺序,先写同步服务的简单实例,然后写异步服务的,最后写4中服务类型的使用。

grpc源码的example目录下都有相关的实例,但是讲的不够清楚,特别是异步服务这一块,注释说明不够详尽,CallData的封装也不利于理接异步服务的流程结构。所以我这里不按照官方的示例来做了。

参考资料

1、编写proto文件,定义服务

如何编写proto文件,可以看Protobuf3 语法指南 (英文原文地址https://developers.google.com/protocol-buffers/docs/proto3)中的说明,这里就不再叙述了。

我这里写一个简单的simple.proto文件,定义三个简单的服务接口,流式接口以后再说。

syntax="proto3";

// 包名是生成代码的使用的namespace,所有代码都在这个下面
package Simple; // 指定服务的名称,作为生成代码里面的二级namespace
service Server {
// 测试接口一
rpc Test1(TestRequest) returns (TestNull ){}
// 测试接口二
rpc Test2(TestNull ) returns (TestReply) {}
// 测试接口三
rpc Test3(TestRequest) returns (TestReply) {}
} message TestNull {
} message TestRequest {
string name = 1; // 客户端名称
int32 id = 2; // 客户端IP
double value = 3; // 附带一个值
} message TestReply {
int32 tid = 1; // 服务线程ID
string svrname = 2; // 服务名称
double takeuptime = 3; // 请求处理耗时
}

上面的接口中,必须有参数和返回值,如果不需要参数或者返回值,也必须定义一个空的(没有成员)message,否则无法通过编译。

2、编译proto文件,生成代码

安装好grpc之后,可以使用grpc的相关命令行程序,来使用proto文件生成C++代码(也可以生成别的语言的),这里需要分两步,一是生成protobuf(反)序列化的代码,二是生成基本服务框架代码。

# 1、生成protobuf序列化代码
# 执行下面命令后,将在当前目录下生成 simple.pb.h 和 simple.pb.cc 文件
protoc -I ./ --cpp_out=. simple.proto # 2、生成服务框架代码
# 执行下面命令后,将在当前目录下生成 simple.grpc.pb.h 和 simple.grpc.pb.cc 文件
protoc -I ./ --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` simple.proto
# 上面的 `which grpc_cpp_plugin` 也可以替换为 grpc_cpp_plugin 程序的路径(如果不在系统PATH下)
# 生成的代码需要依赖第一步生成序列化代码,所以在使用的时候必须都要有(生成的时候不依赖)

如果自己生成比较麻烦,可以在这个网站上生成 protobuf compiler

3、编写服务端代码

查看上一步骤生成的代码(simple.grpc.pb.cc),可以看到它已经生成了服务代码Simple::Server::Service类,里面已经有三个Test函数的空实现,这三个函数都是虚函数,所以可以继承这个类,并实现这三个函数,也可以直接修改生成的代码,把这三个函数的实现改为自己的实现。

server.cpp 代码


#include "simple.grpc.pb.h"
#include <grpcpp/grpcpp.h> #include <memory>
#include <iostream>
#include <strstream> // 继承自生成的Service类,实现三个虚函数
class ServiceImpl:
public Simple::Server::Service {
public: // Test1 实现都是差不都的,这里只是为了测试,就随便返回点数据了
grpc::Status Test1(grpc::ServerContext* context,
const Simple::TestRequest* request,
Simple::TestNull* response)
override
{
std::ostrstream os;
os << "Client Name = " << request->name() << '\n';
os << "Clinet ID = " << request->id() << '\n';
os << "Clinet Value= " << request->value()<< '\n';
std::string message = os.str();
// grpc状态可以设置message,所以也可以用来返回一些信息
return grpc::Status(grpc::StatusCode::OK,message);
}
// Test2
grpc::Status Test2(grpc::ServerContext* context,
const Simple::TestNull* request,
Simple::TestReply* response)
override
{
response->set_tid(100);
response->set_svrname("Simple Server");
response->set_takeuptime(0.01);
return grpc::Status::OK;
}
// Test3
grpc::Status Test3(grpc::ServerContext* context,
const Simple::TestRequest* request,
Simple::TestReply* response)
override
{
std::ostrstream os;
os << "Client Name = " << request->name() << '\n';
os << "Clinet ID = " << request->id() << '\n';
os << "Clinet Value= " << request->value()<< '\n';
std::string message = os.str(); response->set_tid(__LINE__);
response->set_svrname(__FILE__);
response->set_takeuptime(1.234);
// grpc状态可以设置message
return grpc::Status(grpc::StatusCode::OK,std::move(message));
}
}; int main()
{
// 服务构建器,用于构建同步或者异步服务
grpc::ServerBuilder builder;
// 添加监听的地址和端口,后一个参数用于设置认证方式,这里选择不认证
builder.AddListeningPort("0.0.0.0:33333",grpc::InsecureServerCredentials());
// 创建服务对象
ServiceImpl service;
// 注册服务
builder.RegisterService(&service);
// 构建服务器
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
std::cout<<"Server Runing"<<std::endl;
// 进入服务处理循环(必须在某处调用server->Shutdown()才会返回)
server->Wait();
return 0;
}

编译

写完代码之后,需要进行编译。编译的命令如下:

g++ -o server server.cpp simple.grpc.pb.cc simple.pb.cc \
-std=c++11 -I. `pkg-config --cflags protobuf grpc` \
`pkg-config --libs protobuf grpc++ grpc` \
-pthread -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed -ldl # 因为我这里没有grpc.pc文件,导致pkg-config找不到相关配置,所以我实际使用下面命令编译
g++ -o service service.cpp simple.grpc.pb.cc simple.pb.cc -std=c++11 -I. -lgrpc++ -lgrpc -lprotobuf -lgpr -lz -lcares -laddress_sorting -lpthread -Wno-deprecated

这里自己编译的grpc或者安装的可能没有grpc.pc文件,导致pkg-config程序找不到相关的配置,如果没有就直接链接以下的库即可:

# VC下使用
grpc.lib grpc++.lib libprotobuf.lib gpr.lib zlib.lib cares.lib address_sorting.lib ws2_32.lib
# gcc/clang下使用
-lgrpc++ -lgrpc -lprotobuf -lgpr -lz -lcares -laddress_sorting -lpthread

4、编写客户端代码

客户端代码比服务端简单多了,下面直接给出代码(因为比较简单,这里就只写Test3的调用代码了)。

关于客户端,可以看一下这篇文章从grpc源码讲起(Client端的消息发送)

client.cpp代码

客户端这里只写了一个接口的测试,另外两个也都是差不多的,就不写了。

#include "simple.grpc.pb.h"
#include <grpcpp/grpcpp.h> #include <memory>
#include <iostream> int main()
{
// 创建一个连接服务器的通道
std::shared_ptr<grpc::Channel> channel =
grpc::CreateChannel("localhost:33333", grpc::InsecureChannelCredentials());
// 创建一个stub
std::unique_ptr<Simple::Server::Stub> stub = Simple::Server::NewStub(channel); // 上面部分可以复用,下面部分复用的话要自己考虑多线程安全问题
{
// 创建一个请求对象,用于打包要发送的请求数据
Simple::TestRequest request;
// 创建一个响应对象,用于解包响要接收的应数据
Simple::TestReply reply; // 创建一个客户端上下文。它可以用来向服务器传递附加的信息,以及可以调整某些RPC行为
grpc::ClientContext context;
// 发送请求,接收响应
grpc::Status st = stub->Test3(&context,request,&reply);
if(st.ok()){
// 输出下返回数据
std::cout<< "tid = " << reply.tid()
<< "\nsvrname = " << reply.svrname()
<< "\ntakeuptime = " << reply.takeuptime() << std::endl;
}
else {
// 返回状态非OK
std::cout<< "StatusCode = "<< st.error_code()
<<"\nMessage: "<< st.error_message() <<std::endl;
} } }

客户端的编译和服务是一样的,只是把server.cpp改为client.cpp即可。

g++ -o client client.cpp simple.grpc.pb.cc simple.pb.cc -std=c++11 -I. -lgrpc++ -lgrpc -lprotobuf -lgpr -lz -lcares -laddress_sorting -lpthread -Wno-deprecated

5、简单测试一下

编译了serverclient后,都运行起来测试了一下,可行。

服务端这里输出,是因为我在代码里面加了一行输出,在Test3函数中输出了一下函数名(__func__)和行号(__LINE__)。

这里推荐一个工具BloomRPC ,这是一个GRPC服务的可视化界面客户端程序,可以直接加载proto文件,发送请求并接收返回。(我这里无法发送到非本地服务器,不知道是不是这个软件的原因)

grpc使用记录(二)简单同步服务实例的更多相关文章

  1. grpc使用记录(三)简单异步服务实例

    目录 grpc使用记录(三)简单异步服务实例 1.编写proto文件,定义服务 2.编译proto文件,生成代码 3.编写服务端代码 async_service.cpp async_service2. ...

  2. WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[下篇]

    原文:WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[下篇] 在[第2篇]中,我们深入剖析了单调(PerCall)模式下WCF对服务实例生命周期的控制,现在我们来 ...

  3. 搭建简单Django服务并通过HttpRequester实现GET/POST http请求提交表单

    调试Django框架写的服务时,需要模拟客户端发送POST请求,然而浏览器只能模拟简单的GET请求(将参数写在url内),网上搜索得到了HttpRequester这一firefox插件,完美的实现了模 ...

  4. 使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(二)(代码篇)

    这篇是上一篇的延续: 用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(一) 源代码在github上可以下载,地址:https://github.com/guoxia ...

  5. java版gRPC实战之二:服务发布和调用

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  6. Spring Cloud Netflix Zuul 重试会自动跳过经常超时的服务实例的简单说明和分析

    在使用E版本的Spring Cloud Netflix Zuul内置的Ribbon重试功能时,发现Ribbon有一个非常有用的特性: 如果某个服务的某个实例经常需要重试,Ribbon则会在自己维护的一 ...

  7. [WCF REST] 一个简单的REST服务实例

    Get:http://www.cnblogs.com/artech/archive/2012/02/04/wcf-rest-sample.html [01] 一个简单的REST服务实例 [02] We ...

  8. 基于Tcp协议的简单Socket通信实例(JAVA)

    好久没写博客了,前段时间忙于做项目,耽误了些时间,今天开始继续写起~ 今天来讲下关于Socket通信的简单应用,关于什么是Socket以及一些网络编程的基础,这里就不提了,只记录最简单易懂实用的东西. ...

  9. Java学习-007-Log4J 日志记录配置文件详解及实例源代码

    此文主要讲述在初学 Java 时,常用的 Log4J 日志记录配置文件详解及实例源代码整理.希望能对初学 Java 编程的亲们有所帮助.若有不足之处,敬请大神指正,不胜感激!源代码测试通过日期为:20 ...

随机推荐

  1. Shell中根据svn是否有待更新的版本去决定是执行maven打包

    1- svn 更新判断代码 本着学习的目的,这里使用了两种获取version的方法. localVersion=$(svn info -R | grep "Revision\:" ...

  2. Webpack 打包工具

    1. webpack 概念 [文档地址](https://www.webpackjs.com/concepts/) 2. webpack 命令使用及相关工具包 1. webpack 安装和打包命令: ...

  3. Docker 0x06: Docker Volume卷

    目录 Docker Volume卷 一句话什么是docker volume? docker volume特性 docker 挂载卷 docker 多容器间共享数据券 删除,查看数据卷 备份还原数据卷 ...

  4. JMETER 使用BeanShell 配合 if 控制器实现逻辑控制

    业务场景 在登录后,我们根据登录的响应,判断是否执行下一步的操作. 实现步骤 1.在登录采样器树中增加BeanShell 监听器. 作用是在线程上下文变量中增加一个变量,表示登录是否成功. beans ...

  5. elasticsearch使用ansj分词器

    目前elasticsearch的版本已经更新到7.0以上了,不过由于客户需要5.2.2版本的elasticsearch,所以还是需要安装的,并且安装上ansj分词器.在部署ES的时候,采用容器的方式进 ...

  6. python正则表达式(3)--match方法

    1.re.match函数 re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回None. (1)函数语法: re.match(pattern, st ...

  7. 分布式中的分库分表之后,ID 主键如何处理?

    面试题 分库分表之后,id 主键如何处理?(唯一性,排序等) 面试官心理分析 其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定 ...

  8. hashCode、HashMap 的原理

    hashCode 的契约是:如果两个对象相等,那么调用两个对象的 hashCode() 方法一定会返回相同的 hash 值. HashMap 中 存储桶 的原理: 当你在 hashMap 中存储值的时 ...

  9. cmds jdbc连接写法

    格式一:  Oracle JDBC Thin using a ServiceName: jdbc:oracle:thin:@//<host>:<port>/<servic ...

  10. SpringBoot——报错总结

    前言 记录SpringBoot的相关报错信息 错误 无法引入@ResponseBody和@RequestMapping("/") <dependency> <gr ...