目 录CONTENT

文章目录
Go

Golang中使用GRPC&protobuf协议

Hello!你好!我是村望~!
2023-05-08 / 0 评论 / 0 点赞 / 183 阅读 / 2,388 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

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

protobuf/releases下载地址

下载后添加到对应的环境变量!

只要将 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";

接下来,就是消息定义。消息只是包含一组类型字段的集合。

许多标准的简单数据类型都可用作字段类型;包括 boolint32floatdoublestring

您还可以通过使用其他消息类型作为字段类型来为消息添加更多结构。

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)类型 - 在这里您希望指定电话号码可以是 MOBILEHOMEWORK 之一。[一看就是机翻]

将 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 的服务端和客户端代码。

image-20230508221328157

我们分别去实现服务端和客户端来完成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)
}

调用效果:

image-20230508223345309

0

评论区