go gprc 使用 教程
技术栈
grpc
go
protobuff
1.环境
1.1. 安装protoc
项目地址 https://github.com/protocolbuffers/protobuf
下载protobuff, 有条件的github上直接下载,github下载地址,或者可以从maven仓库下载
在maven仓库中找到对应的版本,进行下载,linux ,windows 都有
这里我下载 windows 64 位的这个
下载下来的是可执行文件,直接放到环境变量里面,这个文件名太长了,把后缀都去掉
我放的路径 %GOPATH%\bin\protoc.exe
1.2. 安装 protoc-gen-go
直接go get -u github.com/golang/protobuf/protoc-gen-go
go get 的比较慢的话可以用代理。需要配置下代理。
看下 %GOPATH%\bin\ 有没有protoc-gen-go.exe ,没有的话需要找到下载的包进行安装。
下载目录在:%GOPATH%\pkg\mod\github.com\golang\protobuf@xxx\protoc-gen-go
进入目录然后 go install ,然后再去看bin 目录就会生成protoc-gen-go.exe
2. 建项目实践
1. 建目录 grpcprj
go mod init grpcprj
这个是我创建后的目录
| go.mod #工程文件
| go.sum #工程文件
+---client #客户端代码存放目录
| main.go
+---proto #proto 文件存放目录,包括通过protoc 编译后的文件
| \---hello
| hello.pb.go #protoc 根据proto文件生成的go项目文件
| hello.proto #proto 文件
\---server #服务端代码
| main.go
\---handler #服务端接口实现
hello.go
2. 编写proto文件
syntax="proto3"; //版本
package go.rpc.srv.hello; //作用域
service Hello{ //service 表示服务,在这里面定义接口
//定义了一个接口SayHello 接收了一个Say消息,返回一个Say消息
rpc SayHello(Say) returns (Say);
}
// 定义一个结构体,Say
message Say {
string name = 1; // 定义格式如下 :<修饰符> 类型 字段名 = 唯一编号
//由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。
//在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。
//注意[packed=true]只能用在 repeated修饰的数字类型中
//repeated用来定义数组
repeated int32 list =2; //repeated
//定义map
map maps = 3;
//有时候你需要保留一些你以后要用到的编号或者变量名,使用reserved关键字
reserved 3, 15, 9 to 11; //保留 3,15,9到11
reserved "foo", "bar";
//string var2 = 3;//编译会报错,因为3被保留了
//string var3 = 10;//编译会报错,因为10被保留了
//string foo = 12;//编译会报错,因为foo被保留了
}
消息体可以嵌套,也可以引入其他的proto文件
import "google/protobuf/any.proto"; //引入其他文件
//消息嵌套
message ParentBean{
ChildA child = 1;
message ChildB{
string name = 1;
}
ChildB child2 = 2 ;
}
message ChildA{
string name = 1;
}
字段类型对比
.proto Type | Go Type | 说明 |
---|---|---|
double | float64 | |
float | float32 | |
int32 | int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 |
uint32 | uint32 | 使用变长编码 |
uint64 | uint64 | 使用变长编码 |
sint32 | int32 | 使用变长编码,这些编码在负值时比int32高效的多 |
sint64 | int64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 |
fixed32 | uint32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 |
fixed64 | uint64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 |
sfixed32 | int32 | 总是4个字节 |
sfixed64 | int64 | 总是8个字节 |
bool | bool | 默认 false |
string | string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本,默认值为空。 |
bytes | []byte | 默认是空数组 |
3. 编译proto文件
proto 文件编写完毕以后,使用protoc 进行编译,编译成成 xxx.pb.go,编译是干什么呢?
protoc --go_out=plugins=grpc:. proto/hello/hello.proto
运行命令后会在 proto 文件同目录生成 xxx.pb.go,对应的service 会成生成一个空的实现(指的是只做了实现,并没有具体业务功能),这里需要注意的是,默认go环境变量已经配置正确,gopath\bin 在环境变量里面。
hello.proto(最简的)
syntax="proto3";
service Hello{
rpc SyaHello(Say) returns (Say);
}
message Say {
string name = 1;
}
编译后的文件
...省略了其他代码
// HelloServer is the server API for Hello service.
// helloServer 接口
type HelloServer interface {
SyaHello(context.Context, *Say) (*Say, error)
}
// UnimplementedHelloServer can be embedded to have forward compatible implementations.
type UnimplementedHelloServer struct {
}
// 以下是一个空的实现,没有实现任何业务逻辑
func (*UnimplementedHelloServer) SyaHello(context.Context, *Say) (*Say, error) {
return nil, status.Errorf(codes.Unimplemented, "method SyaHello not implemented")
}
4. 实现服务端接口逻辑
服务端可以实现,也可以不实现,也可以改下上面的默认生成的这个实现类,这里重新实现了下,里面写自己的业务逻辑。
server/handler/hello.go(最简实现)
package handler
import (
"context"
"fmt"
h "grpcprj/proto/hello"
)
type HelloImpl struct {}
func (hi *HelloImpl)SyaHello(ctx context.Context,in *h.Say) (*h.Say, error){
msg:=fmt.Sprintf("echo say:%s",in.GetName())
return &h.Say{Name:msg},nil
}
5. 服务端代码
package main
import (
"context"
"google.golang.org/grpc"
h "grpcprj/proto/hello"
"log"
"time"
)
var(
addr ="localhost:8081"
defmsg = "hello boger"
)
func main() {
var(
conn *grpc.ClientConn
ctx context.Context
cancel context.CancelFunc
err error
r *h.Say
)
if conn,err=grpc.Dial(addr,grpc.WithInsecure(),grpc.WithBlock());err!=nil{
log.Fatalf("conn server err :%v",err)
}
defer conn.Close()
c := h.NewHelloClient(conn)
ctx,cancel=context.WithTimeout(context.Background(),time.Second)
defer cancel()
if r,err=c.SyaHello(ctx,&h.Say{Name:defmsg});err!=nil{
log.Fatalf("get error :%v",err)
}
log.Printf("get msg :%s",r.Name)
}
6. 客户端代码
protoc 生成的go 代码里面包括服务端的定义,和客户端的定义(协议的打包,解包都包括了)
grpcprj/client/hello.go
package main
import (
"context"
"google.golang.org/grpc"
h "grpcprj/proto/hello"
"log"
"time"
)
var(
addr ="localhost:8081"
defmsg = "hello boger"
)
func main() {
var(
conn *grpc.ClientConn
ctx context.Context
cancel context.CancelFunc
err error
r *h.Say
)
if conn,err=grpc.Dial(addr,grpc.WithInsecure(),grpc.WithBlock());err!=nil{
log.Fatalf("conn server err :%v",err)
}
defer conn.Close()
c := h.NewHelloClient(conn)
ctx,cancel=context.WithTimeout(context.Background(),time.Second)
defer cancel()
if r,err=c.SyaHello(ctx,&h.Say{Name:defmsg});err!=nil{
log.Fatalf("get error :%v",err)
}
log.Printf("get msg :%s",r.Name)
}