Golang中使用GRPC&protobuf协议
简单了解
https://grpc.io/docs/languages/go/quickstart/
Protocol Buffers是google开源的一种结构数据序列化机制,可跨语言、跨平台。
相比XML、JSON、Thrift等其他序列化格式,Protocol Buffers的序列化和反序列化性能是很高的,且Protocol Buffers序列化后是二进制流,因此数据大小,传输速度以及保密性都是很好的。
另外!我们在不同语言之间使用rpc通信,不需要单独的为一个服务,各自单独实现对应语言的客户端代码!我们可以单纯去维护 protobuf
文件,代码可以通过插件直接生成!
安装 protoc/protoc-gen-go/protoc-gen-go-grpc
下载后添加到对应的环境变量!
只要将 GOPATH 配置好 然后把 protoc 放入 GOPATH/bin中即可
export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$PATH
命令测试
xiaohao@xiaohaodeMac-mini ~ % protoc --version
libprotoc 22.4
使用以下命令安装Go的协议编译器插件:
如遇到超时可以提前设置代理 (export GOPROXY=https://proxy.golang.com.cn,direct
)
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 # protoc-gen-go是go版本的 Protobuf 编译器插件
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 # protoc-gen-go-grpc 识别 rpc service 编译生成服务/客户端grpc代码
使用 go install
安装的 直接会安装在 GOPATH/bin 目录下面!
xiaohao@xiaohaodeMac-mini bin % pwd
/Users/xiaohao/go/bin
xiaohao@xiaohaodeMac-mini bin % ls
protoc protoc-gen-go protoc-gen-go-grpc
Defining定义协议格式[文档demo]
我们这里要创建一个地址簿的应用,首先我们创建一个.proto
文件来描述我们的应用数据
.proto
文件中的定义很简单:为要序列化的每个数据结构添加一条消息,然后为消息中的每个字段指定名称和类型。
.proto
文件以声明proto
版本和包声明开头 -> 这有助于防止不同项目的使用proto
版本不同造成序列化数据异常和防止不同项目之间的命名冲突。
syntax = "proto3"; // 版本
package addressBook; //包声明
import "google/protobuf/timestamp.proto";
接下来,就是消息定义。消息只是包含一组类型字段的集合。
许多标准的简单数据类型都可用作字段类型;包括 bool
、int32
、float
、double
和 string
。
您还可以通过使用其他消息类型作为字段类型来为消息添加更多结构。
syntax = "proto3"; // 版本
package addressBook; //包声明
import "google/protobuf/timestamp.proto";
option go_package = "./pb"; // go_package 指定了编译后生成文件的路径,也对应了生成后的包名!
// 描述一个Person 对象的message体
message Person {
string name = 1; //编号
int32 id = 2;
string email = 3;
//PhoneType 枚举类型电话类型
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4; //repeated 可以理解为数组 message可以当作自定义的数据类型
google.protobuf.Timestamp last_updated = 5;
}
message AddressBook {
repeated Person people = 1;
}
在上面的例子中Person
消息体包含 PhoneNumber
消息体,而 AddressBook
消息包含 Person
消息。
您甚至可以定义嵌套在其他消息中的消息类型——如您所见,PhoneNumber
类型是在 Person
中定义的。
如果您希望您的字段之一具有预定义的值列表之一,您还可以定义枚举(enum)
类型 - 在这里您希望指定电话号码可以是 MOBILE
、HOME
或 WORK
之一。[一看就是机翻]
将 proto 文件编译为 go 代码!
protoc -I=. ./proto/addressbook.proto --go_out=./proto
具体各参数含义如下:
-I=.:指定 Protocol Buffer 文件所在的根目录为当前目录。
--go_out=./proto:指定输出目录为 ./proto。
./proto/addressbook.proto:要编译的 Protocol Buffer 文件路径。
如果遇到了下面的问题:
➜ microway git:(GRPC) ✗ protoc -I=. --go_out=./proto ./proto/addressbook.proto
google/protobuf/timestamp.proto: File not found.
解决方法:我们安装的时候不仅要把 protoc 可执行文件放入 GOPATH 下载下的 google 文件夹也要放进去,里面放了很多内置的包
➜ bin pwd
/Users/xiaohao/go/bin
➜ bin ll
total 45104
drwxrwxr-x@ 4 xiaohao staff 128B 5 8 19:08 google
-r-xr-xr-x@ 1 xiaohao staff 5.3M 5 4 02:10 protoc
-rwxr-xr-x@ 1 xiaohao staff 8.4M 5 8 17:44 protoc-gen-go
-rwxr-xr-x 1 xiaohao staff 8.3M 5 8 17:45 protoc-gen-go-grpc
➜ bin
...
cd protobuf
➜ protobuf ll
total 216
-rw-r--r--@ 1 xiaohao staff 5.9K 1 1 1980 any.proto
-rw-r--r--@ 1 xiaohao staff 7.5K 1 1 1980 api.proto
drwxrwxr-x@ 3 xiaohao staff 96B 5 4 02:10 compiler
-rw-r--r--@ 1 xiaohao staff 40K 1 1 1980 descriptor.proto
-rw-r--r--@ 1 xiaohao staff 4.8K 1 1 1980 duration.proto
-rw-r--r--@ 1 xiaohao staff 2.3K 1 1 1980 empty.proto
-rw-r--r--@ 1 xiaohao staff 8.0K 1 1 1980 field_mask.proto
-rw-r--r--@ 1 xiaohao staff 2.3K 1 1 1980 source_context.proto
-rw-r--r--@ 1 xiaohao staff 3.7K 1 1 1980 struct.proto
-rw-r--r--@ 1 xiaohao staff 6.3K 1 1 1980 timestamp.proto # 我们需要的 timestamp
-rw-r--r--@ 1 xiaohao staff 6.0K 1 1 1980 type.proto
-rw-r--r--@ 1 xiaohao staff 3.9K 1 1 1980 wrappers.proto
OK! 继续去编译!
编译成功后! 可以看到已经生成了 addressbook.pb.go
文件!
➜ microway git:(GRPC) ✗ tree
.
├── go.mod
└── proto
├── addressBook.proto
└── pb
└── addressbook.pb.go
3 directories, 3 files
然后文件内部有飘红的!因为一些依赖问题!
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
执行 go mod tidy
同步一下就可以了!
然后我们可以在其他地方调用生成的 go 代码
package main
import (
"fmt"
"google.golang.org/protobuf/types/known/timestamppb"
"microway/proto/pb"
)
func main() {
p := pb.Person{
Name: "cc",
Id: 0,
Email: "cc@gmail.com",
Phones: nil,
LastUpdated: timestamppb.Now(),
}
fmt.Println(p.Name) // cc
}
proto的序列化和反序列化
package main
import (
"fmt"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
"microway/proto/pb"
)
func main() {
p := pb.Person{
Name: "cc",
Id: 0,
Email: "cc@gmail.com",
Phones: nil,
LastUpdated: timestamppb.Now(),
}
marshal, _ := proto.Marshal(&p)
fmt.Println(string(marshal))
var unP pb.Person
_ = proto.Unmarshal(marshal, &unP)
fmt.Println(unP.Name)
}
cc
cc@gmail.com*
����؏v
cc
protocol buffer 添加 rpc 服务
service 服务名称 {
rpc 函数名(参数:消息体) returns (返回值消息体)
}
Exp:
syntax = "proto3"; // 版本
package user; //包声明
option go_package = "./user";
message AimUser {
string name = 1;
}
service User {
rpc SayHi(AimUser) returns(AimUser); //定义服务的某一个方法,接受的参数,和返回值
}
这里我们执行编译命令的时候就需要加上 plugin
-> protoc-gen-go-grpc
protoc -I=. --go_out=./proto --go-grpc_out=./proto ./proto/grpc_User.proto
参数 -I=.
告诉编译器在当前目录中查找导入的 .proto
文件。
参数 --go_out=./proto
告诉编译器生成用于处理 Protocol Buffer 的 Go 代码,并将其输出到 proto
目录中。
参数 --go-grpc_out=./proto
告诉编译器生成用于处理 gRPC 服务的 Go 代码,并将其输出到 proto
目录中。
最后的参数 ./proto/grpc_User.proto
指定了要为其生成代码的 .proto
文件所在位置。
因此,总体来说,该命令将会生成一个用于处理 Protocol Buffer 和 gRPC 服务的 Go 代码,并将它们输出到 proto
目录中。
└── proto
├── addressBook.proto
├── grpc_User.proto
├── pb
│ └── addressbook.pb.go
└── user
├── grpc_User.pb.go
└── grpc_User_grpc.pb.go
可以看到生成基本go对应的各种结构体相关代码之外,也生成了一个 grpc的go文件! 其中包括 grpc 的服务端和客户端代码。
我们分别去实现服务端和客户端来完成GRPC调用
服务端SERVER
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"log"
"microway/proto/user"
"net"
)
type UserSrv struct {
/*
因为 type UserServer 的 mustEmbedUnimplementedUserServer ,没有导出无法直接实现
type UserServer interface {
SayHi(context.Context, *AimUser) (*AimUser, error)
mustEmbedUnimplementedUserServer()
}
而 user.UnimplementedUserServer 结构体实现了mustEmbedUnimplementedUserServer
所以这里直接使用了类似继承的方式
*/
user.UnimplementedUserServer
}
// 实现对应的方法!
func (u *UserSrv) SayHi(ctx context.Context, aimUser *user.AimUser) (*user.AimUser, error) {
aimUser.Name = "hi " + aimUser.GetName()
fmt.Println("SayHi called")
return aimUser, nil
}
func main() {
// 创建 grpc server
server := grpc.NewServer()
// RegisterUserServer 注册服务
user.RegisterUserServer(server, new(UserSrv))
listen, err := net.Listen("tcp", ":8111") //开启tcp服务
err = server.Serve(listen) //绑定grpc服务到tcp
if err != nil {
log.Fatalf("listen server err %s", err.Error())
}
}
客户端 CLIENT
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"log"
"microway/proto/user"
)
func main() {
// 建立 grpc与服务端套接字通信
conn, err := grpc.Dial(":8111", grpc.WithInsecure())
if err != nil {
return
}
//defer 关闭链接
defer func(conn *grpc.ClientConn) {
err := conn.Close()
if err != nil {
log.Fatalf("关闭 con 套接字失败 err:%s", err.Error())
}
}(conn)
// 创建一个客户端实例
client := user.NewUserClient(conn)
//调用客户端封装好的 访问服务方法
hi, err := client.SayHi(context.Background(), &user.AimUser{
Name: "村望老弟",
})
if err != nil {
return
}
fmt.Println(hi)
}
调用效果:
评论区