protobuf
- protobuf编译器
二进制安装
安装protobuf-go - 数据类型与GO对比
- 消息体
- 数组
- 枚举
- 联合体
- map
- timestamp
- metadata
- 编译protobuf文件
protoc命令 - 添加rpc服务
- 序列化
- 反序列化
protobuf 编译器
二进制安装
1.下载二进制包
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.17.3/protoc-3.17.3-linux-x86_64.zip
2.解压设置环境变量
unzip protoc-3.17.3-linux-x86_64.zip -d protoc
// shell 环境变量
vim /etc/profile
export PROTOC=~/protoc
export PATH=$PATH:$PROTOC/bin
source /etc/profile
//fish 环境变量
vim ~/.config/fish/config.fish
set -x PROTOC /home/wzl/protoc
set -x PATH $PROTOC/bin $PATH
sudo vim /etc/fish/config.fish
source /etc/fish/config.fish
3.测试命令提示
安装protobuf-go
git clone https://github.com/golang/protobuf.git
mv protobuf $GOMODCACHE/github.com/golang/
cd $GOMODCACHE/github.com/golang/protobuf/protoc-gen-go
go build
sudo cp protoc-gen-go /bin
//有自动提示安装成功
数据类型与GO对比
- .proto | GO
double | float64
float | float32
int32 | int32
int64 | int64
uint32 | uint32
uint64 | uint64
sint32 | int32
sint64 | int64
fixed64 | uint32
fixed64 | uint64
bool | bool
string | string
bytes | []byte
消息体
1.定义消息体
//默认是proto2
syntax="proto3";
//指定所在包名
package pb;
//编译生成文件路径及包名
option go_package = "./;proto";
//定义消息体
message Person {
string name = 1;//字段编号,可不从1开始,但不能重复
int32 age = 2;
}
2.消息体嵌套
syntax="proto3";
package pb;
message Person {
string name = 1;
int32 age = 2;
PhoneNumber p = 3; //消息体可嵌套
}
message PhoneNumber {
string number = 1;
int64 type = 2;
}
3.字段编号
1)每个字段唯一编号
2)范围:1 - 536870911,其中19000~19999是协议缓冲区保留数
数组
repeated
repeadted关键字类似与go中的切片,编译之后对应的也是go的切片:
syntax="proto3";
package pb;
message Person {
string name = 1;
int32 age = 2;
message PhoneNumber {
string number = 1;
int64 type = 2;
}
repeated PhoneNumber phone = 3; //数组
}
枚举
enum
syntax="proto3";
package pb;
//定义枚举类型
enum Week {
Monday = 0; //枚举值,必须从0开始,与写入编号无关
Turesday =1;
}
message Person {
string name = 1;
int32 age = 2;
//枚举
Week w = 5;
}
联合体
oneof
syntax="proto3";
package pb;
message Person {
string name = 1;
int32 age = 2;
//联合体
oneof data {
string teacher = 3; //写入编号不能重复
string class = 4;
}
}
map
syntax="proto3";
import "base.proto";
import "google/protobuf/empty.proto";
option go_package = "../pb";
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply);
rpc Ping (google.protobuf.Empty) returns (Pong);
}
message HelloRequest {
string url = 1;
string name = 2;
map<string,string> mp = 4; //map
}
message HelloReply {
string message = 1;
}
timestamp
syntax = "proto3";
import "google/protobuf/timestamp.proto";
option go_package = "./;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
google.protobuf.Timestamp addTime = 5;
}
message HelloReply {
string message = 1;
}
timestamp.proto文件路径
protoc安装路径/include/google/protobuf/timestamp.proto
生成go package路径
vim timestamp.proto
option go_package = "google.golang.org/protobuf/types/known/timestamppb";
$GOMODCACHE/google.golang.org/protobuf@v1.27.1/types/known/timestamppb/timestamp.pb.go
package main
import (
"context"
"fmt"
"grpcStream/timestamp/proto" // 根据proto文件自动生成的代码
"log"
"time"
"google.golang.org/grpc"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
func main() {
// 创建连接
conn, err := grpc.Dial("localhost:50053", grpc.WithInsecure())
if err != nil {
log.Printf("连接失败: [%v]\n", err)
return
}
defer conn.Close()
// 声明客户端
client := proto.NewGreeterClient(conn)
rsp, _ := client.SayHello(context.Background(), &proto.HelloRequest{
AddTime: timestamppb.New(time.Now()),
})
fmt.Println(rsp.Message)
}
metadata
MD类型实际是map,key是string,value是string类型的slice
type MD map[string][]string
1. 创建
//第一种方式
md := metadata.New(map[string]string{"k1":"v1","k2":"v2"})
//第二种方式
md := metadata.Pairs(
"k1","v1",
"k1","v1-2", // k1:[]sring{"v1","v1-2"}
"k2","v2"
)
2. 发送
md := metadata.Pairs("key","val")
ctx := metadata.NewOutgoingContext(context.Background(),md)
response,err := client.SomeRPC(ctx,someRequest)
3. 接收
func (s *server) SomeRPC(ctx context.Context.Context,in pb.SomeRequest) (pb.SomeResponse,error){
md,ok := metadata.FromIncomingContext(ctx)
}
编译protobuf文件
go编译命令
protoc --go_out=./ *.proto ---> xxx.pb.go
c++编译命令
protoc --cpp_out=./ *.proto ---> xxx.pb.cc,xxx.pb.h
编译错误
protoc-gen-go: unable to determine Go import path for "test.proto"
Please specify either:
• a "go_package" option in the .proto source file, or
• a "M" argument on the command line.
修改错误
option go_package = "path/packageName"; // 或 "../pb;pb" 路径;包名
syntax="proto3";
package pb;
option go_package = "./pb"; //生成包路径
message Person {
string name = 1;
int32 age = 2;
PhoneNumber p = 3; //消息体可嵌套
}
message PhoneNumber {
string number = 1;
int64 type = 2;
}
protoc命令
protoc -h
-I=path | --proto_path=PATH path:proto文件目录
--go_out=[paths=source_relative,plugins=grpc:]OUT_DIR
paths参数:
使用 source_relative(相对-I指定的路径) 则不会使用option go_package中指定的路径
使用 import 则是使用option go_package 中指定的路径
参数分开: --go_out=. --go_opt=paths=source_relative,plugins=grpc
添加rpc服务
- 语法
service 服务名 {
rpc 函数名(参数:消息体) returns (返回值:消息体)
}
例
message People {
string name = 1;
}
message Man {
int32 age = 2;
}
service hello {
rpc HelloPeple(People) returns (Man);
}
- 编译期间,protobuf默认不编译服务,要想使之编译。需要使用gRPC.
- 编译指令
protoc --go_out=plugins=grpc:./ *.proto
- import proto文件
base.proto
syntax = "proto3";
option go_package = ".;proto"
message Empty1{
}
#实例化Pong,需要编译生成base.pb.go文件
message Pong {
string id = 1;
}
test.proto
syntax="proto3";
option go_package = ".;proto";
import "base.proto";
import "google/protobuf/empty.proto";
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply);
rpc Ping (google.protobuf.Empty) returns (Pong);
}
message HelloRequest {
string url = 1;
string name = 2;
}
message HelloReply {
string message = 1;
}
google/protobuf/empty.proto
序列化
package main
import (
"ImTransfer/pb"
"fmt"
"google.golang.org/protobuf/proto"
)
func main() {
req := pb.PhoneNumber{
Number: "123",
Type: 11,
}
rsp, _ := proto.Marshal(&req)
fmt.Println(string(rsp))
}
反序列化
import (
"ImTransfer/pb"
"fmt"
"google.golang.org/protobuf/proto"
)
func main() {
req := pb.PhoneNumber{
Number: "123",
Type: 11,
}
rsp, _ := proto.Marshal(&req)
fmt.Println(string(rsp))
//反序列化
newReq := new(pb.PhoneNumber)
_ = proto.Unmarshal(rsp, newReq)
fmt.Println(newReq)
}