一、简介
gRPC 是一个由Google推出的 RPC 框架。RPC(Remote Procedure Call)是指远程调用进程,可以理解为远程调用一个函数。在不同的服务器间,互相调用函数,来达到调用不同的服务,这也被称为微服务。
而 gRPC 将这一过程简化,让你远程调用时就如同本地调用一般得心应手。而且 gRPC 支持多种语言,包括 C/C++、Go、Java、C# 等。
二、安装
1. 系统环境配置
https://github.com/protocolbuffers/protobuf/releases/download/v3.9.0/protoc-3.9.0-win64.zip
下载 protoc 的压缩包并安装,并且将 bin 目录配置到环境变量。
2. 项目环境配置
go install github.com/golang/protobuf/protoc-gen-go
安装 protoc 的 Go 版本生成器,输入命令后根据提示一步步安装即可。可能 Go 编译器会先让你 go get 一些依赖包,然后再用 go install 安装。
三、使用
1. 创建 hello.proto 文件
syntax = "proto3";
//package hello; 指定proto默认包,不是必须
option go_package = "/hello";
import "Req.proto";
import "Res.proto";
service HelloServer {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
2. 创建Req.proto
syntax = "proto3";
option go_package = "/hello";
message HelloRequest {
string name = 1;
string message = 2;
}
3. 创建Res.proto
syntax = "proto3";
option go_package = "/hello";
message HelloResponse {
string name = 1;
string message = 2;
}
4. 命令生成
protoc -I . --go_out=plugins=grpc:. ./hello.proto ./Res.proto ./Req.proto
解释一下命令的各个参数
- -I 是 import 包的路径,也就是输入路径。 . 表示当前目录
- --go_out=plugins:grpc:. 表示生成 Go 的 RPC 文件在当前目录
- 后面三个参数是 proto 的源文件
5. 客户端配置
func main() {
conn, _ := grpc.Dial(":8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
defer conn.Close()
client := hello.NewHelloServerClient(conn)
res, _ := client.Hello(context.Background(), &hello.HelloRequest{
Name: "cwy",
Message: "ok",
})
fmt.Println(res)
}
和服务端建立连接以后,直接使用 client 调用方法。
6. 服务端配置
type HelloServer struct {
}
func (HelloServer) Hello(c context.Context, req *hello.HelloRequest) (*hello.HelloResponse, error) {
return &hello.HelloResponse{
Name: req.Name,
Message: req.Message,
}, nil
}
func main() {
listen, _ := net.Listen("tcp", ":8080")
server := grpc.NewServer()
hello.RegisterHelloServerServer(server, HelloServer{})
fmt.Println("Run:8080")
server.Serve(listen)
}
gRPC 生成的服务是一个接口,所以需要在服务端定义一个结构体来实现。
主函数里监听对应的端口,然后定义一个 gRPC 服务,最后调用生成代码里的注册函数,将服务与实现的结构体绑定,最后在运行服务。
四、proto3 语法介绍
1. 基础语法
proto 作为 gRPC 的源文件,也是类似一门编程语言,有一定的语法。
syntax = "proto3";
//package hello; 指定proto默认包,不是必须
option go_package = "/hello";
import "Req.proto";
import "Res.proto";
service HelloServer {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
以开头的 proto 文件为例。
- syntax 指定 proto 版本,有 proto2 和 proto3 可选,如果不写,默认是 proto2
- package 指定 proto 的默认包,proto3 可以忽略
- option go_package 指定生成的代码的 Go 包
- import 导入其他 proto 文件
- service 生成一个服务,对应 Go 里面的接口
- rpc 对应 Go 里面的函数
- message 生成一个消息,对应 Go 里面的结构体
2. message 消息
message <message_name> {
<field_rule> <field_type> <field_name> = <field_number>;
}
message 对应 Go 里的结构体,gRPC 会生成相应的 struct
- <field_rule>:规则,如 required、optional、repeated
- <field_type>:数据类型
- <field_name>:字段名,和结构体内部的属性一样
- <field_number>:字段的编号,同一个 message 内必须唯一,用以标识字段
3. 基本类型
message test {
double t1 = 1;
float t2 = 2;
int32 t3 = 3;
int64 t4 = 4;
uint32 t5 = 5;
uint64 t6 = 6;
sint32 t7 = 7;
sint64 t8 = 8;
fixed32 t9 = 9;
fixed64 t10 = 10;
sfixed32
sfixed64
bool
string
bytes
}
普通的类型大家一定很熟悉,有一些奇怪的类型是如下意思:
- sint:变长编码。对于负数的处理比 int 编码效率高
- fixed:定长编码。对于比 228/256 大的数的编码效率比 uint 高
- sfixed:定长,相当于 int
- string:必须是 utf-8 或者 7-bit ASCII 编码
- bytes:任意字节数据
4. 复合类型
message test {
enum En {
option allow_alias = true // 开启重复
t1 = 0;
t2 = 1;
t3 = 1;
}
En t1 = 1;
repeated En t2 = 2; // 生成一个 En 的切片
map<int32, string> m = 3;
}
- repeated 是声明数组的方式
- map是无序的,key 必须是可哈希的 string 或者 整型,value 可以是任意类型
- message 还能嵌套 message 外部无法访问