Go 中的 gRPC 入门详解
Go GRPC 入门
1,安装包
grpc
golang-grpc 包提供了 gRPC 相关的代码库,通过这个库我们可以创建 gRPC 服务或客户端,首先需要安装他。
go get -u google.golang.org/grpc
协议插件
要玩 gRPC,自然离不开 proto 文件,需要安装两个包,用于支持 protobuf 文件的处理。
go get -u github.com/golang/protobuf
go get -u github.com/golang/protobuf/protoc-gen-go
注:GOPATH/bin
下有个 protoc-gen-go.exe
文件,然而这个只是 protoc 的插件,他本身不是 protoc 工具。。。
Protocol Buffers
Protocol Buffers 是一个与编程语言无关、与平台无关的可拓展机制,用于序列化结构数据,是一种数据交换格式,gRPC 使用 protoc 作为协议处理工具。
学习 Go 的 gRPC 时,有个坑,很多文章里面都没有说到要安装这个,执行命令提示不存在 protoc 命令。
首先到 https://github.com/protocolbuffers/protobuf/releases 下载 相应的包,例如笔者下载的是 protoc-3.15.6-win64.zip
。
解压后,复制里面的 bin\protoc.exe
文件,复制到 GOPATH\bin
命令,跟 protoc-gen-go.exe
放一起。
测试
以上都妥当后,我们在一个新的目录,创建一个 test.proto 文件,其内容示例如下如下:
注:protoc-3.15.6-win64\include\google\protobuf
目录也有很多示例。
syntax = "proto3";
// 包名
package test;
// 指定输出 go 语言的源码到哪个目录以及文件名称
// 最终在 test.proto 目录生成 test.pb.go
// 也可以只填写 "./"
option go_package = "./;test";
// 如果要输出其它语言的话
// option csharp_package="MyTest";
service Tester{
rpc MyTest(Request) returns (Response){}
}
// 函数参数
message Request{
string jsonStr = 1;
}
// 函数返回值
message Response{
string backJson = 1;
}
然后在 proto 所在目录,执行命令将 proto 转换为相应的编程语言文件。
protoc --go_out=plugins=grpc:. *.proto
会发现在当前目录输出了 test.pb.go
文件。
2,gRPC 服务端
创建一个 go 程序,把 test.pb.go 复制放到在 main.go 目录,在 main.go 引入 grpc:
import (
"context"
"fmt"
"google.golang.org/grpc"
// test.pb.go 默认包名是 package 为 main,不需要在这里引入
"google.golang.org/grpc/reflection"
"log"
"net"
)
在 test.pb.go
中,生成了两个个 Tester
的接口,我们来看一下这两个接口的定义:
type TesterServer interface {
MyTest(context.Context, *Request) (*Response, error)
}
type TesterClient interface {
MyTest(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
要实现 proto 中的服务,则需要我们实现 TesterServer
接口,要编写 客户端,则需要实现 TesterClient
。
这里我们先实现 Server。
// 用于实现 Tester 服务
type MyGrpcServer struct{}
func (myserver *MyGrpcServer) MyTest(context context.Context, request *Request) (*Response, error) {
fmt.Println("收到一个 grpc 请求,请求参数:", request)
response := Response{BackJson: `{"Code":666}`}
return &response, nil
}
接着我们创建 gRPC 服务。
func main() {
// 创建 Tcp 连接
listener, err := net.Listen("tcp", ":8028")
if err != nil {
log.Fatalf("监听失败: %v", err)
}
// 创建gRPC服务
grpcServer := grpc.NewServer()
// Tester 注册服务实现者
// 此函数在 test.pb.go 中,自动生成
RegisterTesterServer(grpcServer, &MyGrpcServer{})
// 在 gRPC 服务上注册反射服务
// func Register(s *grpc.Server)
reflection.Register(grpcServer)
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
3,gRPC 客户端
创建一个新的 go 项目,把 test.pb.go 复制放到 main.go 同级目录,main.go 的代码:
package main
import (
"bufio"
"context"
"google.golang.org/grpc"
"log"
"os"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8028", grpc.WithInsecure())
if err != nil {
log.Fatal("连接 gPRC 服务失败,", err)
}
defer conn.Close()
// 创建 gRPC 客户端
grpcClient := NewTesterClient(conn)
// 创建请求参数
request := Request{
JsonStr: `{"Code":666}`,
}
reader := bufio.NewReader(os.Stdin)
for {
// 发送请求,调用 MyTest 接口
response, err := grpcClient.MyTest(context.Background(), &request)
if err != nil {
log.Fatal("发送请求失败,原因是:", err)
}
log.Println(response)
reader.ReadLine()
}
}
4,编译运行
由于创建的时候,test.pb.go 使用的包名是 main,所以在编译时,需要把多个 go 文件一起编译:
go build .\main.go .\test.pb.go
然后分别启动 server 和 client,在 client 每按下一次回车键,便发送一次 gRPC 消息。
到这里,我们学习了一个完整的 gRPC 从创建协议到创建服务和客户端的过程,下面将接着学习一些相关的知识,了解一些细节。
5,其它
proto.Marshal
可以对请求的参数进行序列化,如:
// 创建请求参数
request := Request{
JsonStr: `{"Code":666}`,
}
out,err:= proto.Marshal(&request)
if err != nil {
log.Fatalln("Failed to encode address book:", err)
}
if err := ioutil.WriteFile("E:/log.txt", out, 0644); err != nil {
log.Fatalln("Failed to write address book:", err)
}
而 proto.Unmarshal
则可以反序列化。
我们还可以自定义如何序列化反序列化消息,代码示例:
b, err := MarshalOptions{Deterministic: true}.Marshal(m)
感兴趣的读者可访问 https://pkg.go.dev/google.golang.org/protobuf/proto#MarshalOptions
GRPC
Protobuf buffer
Protobuf buffer 是一种数据格式,而 Protobuf 是 gRPC 协议,这里需要区分一下。
protobuf buffer 是 Google 用于序列化结构话数据的开源机制,要定义一个 protobuf buffer,需要使用 message 定义。
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}
开源看到,每个字段都有一个 数字, = 1
这个不是赋值,而是编号。一个 message 中,每个字段都有唯一的编号,这些数字用于标识二进制格式的字段(数据传输时会被压缩等),当编号范围是 1-15 时,存储编号需要一个字节,也就是说 message 中的字段尽量不超过 15 个,1-15 编号用来定义频繁出现的消息元素。当然,也可以使用16-2047
之间的数字作为编号,此时存储编号需要两个字节。
详细的说可以参考官方文档:
https://developers.google.com/protocol-buffers/docs/overview
字段类型
字段类型就不详细列表了,读者可以参考官方文档,这里列一下常用的数据类型:
double、float、int32、int64、bool、string、bytes、枚举。
由于 gRPC 需要考虑兼容 C 语言、C#、Java、Go 语言等,所以 gRPC 中的类型不等同于编程语言中的相关类型。这些类型都是 gRPC 中定义的,并且如果要转换为编程语言中的类型,需要一些转换机制,而这有时会十分麻烦。
字段规则
每个字段都可以指定一个规则,在定义字段类型的开头使用规则标识。
有以下三种规则:
required
:格式正确的消息必须恰好具有此字段之一,即必填字段。optional
:格式正确的消息可以包含零个或一个此字段(但不能超过一个,即值是可选的。repeated
:在格式正确的消息中,此字段可以重复任意次(包括零次),重复值的顺序将保留,表示该字段可以包含0~N个元素。
由于历史原因,repeated
标量数字类型的字段编码效率不高。新代码应使用特殊选项[packed=true]
来获得更有效的编码。例如:
repeated int32 samples = 4 [packed=true];
在可选字段中 optional 中,我们可以为其设置一个默认值,当传递消息时如果没有填写此字段,则使用其默认值:
optional int32 result_per_page = 3 [default = 10];
Protobuf
接下来将介绍 gRPC 的协议格式(protobuf),下面是官方文档的一个示例:
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
syntax 指明协议的版本;
package 指明该 .proto 的名称;
import 关键字可以在当前 .proto 中引入其它 .proto 文件,gRPC 基本数据类型中不包含时间格式,可以引入 timestamp.proto
。
不同编程语言引入包/库的方式是不同的,C++ 和 C# 都是使用命名空间区分代码位置;Java 以目录、公共类严格区别包名;go 则是以一个 .go 文件任意设置 package 名称。
前面提到了 protoc,可以将协议文件转为为具体的代码。
为了兼容各种编程语言,我们协议设置 _package
,这样可以支持生成不同语言代码时设置包/库名称。
例如 :
option go_package = "Test"; // ...
option csharp_package = "MyGrpc.Protos"; // 生成命名空间 namespace MyGrpc.Protos{}
option java_paclage = "MyJava.Protos"; // ...
gRPC 四种服务方法
protobuf 中除了可以定义 message,也可以定义流式接口。
gRPC使您可以定义四种服务方法:
一元 RPC,客户端向服务器发送单个请求并获得单个响应,就像普通的函数调用一样。前面我们提到的都是一元 gRPC。
rpc SayHello(HelloRequest) returns (HelloResponse);
服务器流式RPC,客户端在其中向服务器发送请求,并获取流以读取回一系列消息。客户端从返回的流中读取,直到没有更多消息为止。gRPC保证在单个RPC调用中对消息进行排序。
客户端 -> 服务端 -> 返回流 -> 客户端 -> 接收流
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
客户端流式RPC,客户端在其中编写消息序列,然后再次使用提供的流将其发送到服务器。客户端写完消息后,它将等待服务器读取消息并返回其响应。gRPC再次保证了在单个RPC调用中的消息顺序。
客户端 -> 发送流 -> 服务端 -> 接收流 ->
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
双向流式RPC,双方都使用读写流发送一系列消息。这两个流独立运行,因此客户端和服务器可以按照自己喜欢的顺序进行读写:例如,服务器可以在写响应之前等待接收所有客户端消息,或者可以先读取消息再写入消息,或读写的其他组合。每个流中的消息顺序都会保留。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
编译 proto
前面我们用 protoc 来编译 .proto 文件为 go 语言,为了支持编译为 go,需要安装 protoc-gen-go
插件,C# 可以安装 protoc-gen-zsharp
插件。
需要注意的是,转换 .proto 为编程语言,不一定要安装 protoc。
例如 C# 只需要把 .proto 文件放到项目中,通过包管理器安装一个库,就会自动转换为相应的代码。
回归正题,聊一下 protoc 编译 .proto 文件的命令。
protoc 常用的参数如下:
--proto_path=. #指定proto文件的路径,填写 . 表示就在当前目录下
--go_out=. #表示编译后的文件存放路径;如果编译的是 csharp,则 --csharp_out
--go_opt={xxx.proto}={xxx.proto的路径}
# 示例:--go_opt=Mprotos/bar.proto=example.com/project/protos/foo
最简单的编译命令:
protoc --go_out=. *.proto
--{xxx}_out
指令是必须的,因为要输出具体的编程语言代码。
这个输出文件的路径是执行命令的路径,如果我们不在 .proto 文件目录下执行命令,则输出的代码便不是相同位置了。为了解决这个问题,我们可以使用:
--go_opt=paths=source_relative
这样在别的地方执行命令,生成的代码会跟 .proto 文件放在相同的位置。
Go 中的 gRPC 入门详解的更多相关文章
- vue2.x版本中computed和watch的使用入门详解-computed篇
前言 在基于vue框架的前端项目开发过程中,只要涉及到稍微复杂一点的业务,我们都会用到computed计算属性这个钩子函数,可以用于一些状态的结合处理和缓存的操作. 基础使用 在computed中,声 ...
- vue2.x版本中computed和watch的使用入门详解-watch篇
前言 watch顾名思义,属于vue2.x版本中,监听和观察组件状态变化的钩子函数,常见的应用场景有监听路由变化,以及父组件传递给子组件的props数据的变化等 基本使用 在使用watch的时候,需要 ...
- vue2.x版本中computed和watch的使用入门详解-关联和区别
前面两篇介绍了computed和watch的基本使用 watch篇 computed篇 两者的区别,继续通过代码实现的方式具体去了解 html <li>最开始的value值:{{ name ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- SQL注入攻防入门详解
=============安全性篇目录============== 本文转载 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机 ...
- SQL注入攻防入门详解(2)
SQL注入攻防入门详解 =============安全性篇目录============== 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱 ...
- Quartz 入门详解
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个, ...
- Redis快速入门详解
Redis入门详解 Redis简介 Redis安装 Redis配置 Redis数据类型 Redis功能 持久化 主从复制 事务支持 发布订阅 管道 虚拟内存 Redis性能 Redis部署 Redis ...
- [转]SQL注入攻防入门详解
原文地址:http://www.cnblogs.com/heyuquan/archive/2012/10/31/2748577.html =============安全性篇目录============ ...
随机推荐
- TypeScript constructor public cause duplicate bug
TypeScript constructor public cause duplicate bug constructor public const log = console.log; // con ...
- free online markdown editor
free online markdown editor markdown https://blog.csdn.net/xgqfrms/article/details/50129317 In-brows ...
- c++ 使用进程id获取打开的网络端口
#pragma warning( disable : 4996) #include <winsock2.h> #include <ws2tcpip.h> #include &l ...
- 运行Chrome浏览器如何添加Options
原文档 Options Windows: 退出Chrome的所有正在运行的实例. 右键单击"Chrome"快捷方式 选择属性 在"目标:"行的末尾添加命令行标志 ...
- SpringBoot整合开发
1.SpringBoot分模块 分模块就是将一个项目分成多个模块,即maven项目. 1)首先创建一个springboot的项目: 第一步:选择springboot的项目 第二步:填写项目的相关信息, ...
- 若依管理系统RuoYi-Vue(三):代码生成器原理和实战
历史文章 若依管理系统RuoYi-Vue(一):项目启动和菜单创建 若依管理系统RuoYi-Vue(二):权限系统设计详解 本篇文章将会讲解ruoyi-vue系统下代码生成器的使用.原理分析以及将这部 ...
- docker里运行docker命令
一.概述 现有环境的jenkins是在docker里面运行的,需要执行docker相关命令才行. 关于基于docker搭建jenkins,请参考链接: https://www.cnblogs.com/ ...
- linux的0,1,2号进程
一.答案 https://blog.csdn.net/gatieme/article/details/51532804 仔细读一下作者的博客,都是操作系统底层相关. 二.补充: 1.linux代码,a ...
- 01.从0实现一个JVM语言之架构总览
00.一个JVM语言的诞生过程 文章集合以及项目展望 源码github地址 这一篇将是架构总览, 将自顶向下地叙述自制编译器的要素; 文章目录 01.从0实现一个JVM语言之架构总览 架构总览目前完成 ...
- C++的标识符的作用域与可见性
下面是关于C++的标识符的作用域与可见性学习记录,仅供参考 标识符的作用域与可见性 作用域是一个标识符在程序正文中有效的区域. 作用域分类 ①函数原型作用域 ②局部作用域(快作用域) ③类作用域 ④文 ...