概念

protoBuffer 简称 protobuf,是Google推出的一种新型数据存储格式,压缩性好,传输速度快。序列化反序列话速度快。性能强于json, xml,支持多种语言。

protobuf中所有的字段都是可选的,没种类型都有相应的默认值。定义的参数不传值也是有默认值的,例如枚举默认值为0

环境安装

protoc工具下载

protoc工具下载地址

查看可输出的语言
protoc | grep OUT_DIR

--cpp_out=OUT_DIR           Generate C++ header and source.
--csharp_out=OUT_DIR        Generate C# source file.
--java_out=OUT_DIR          Generate Java source file.
--js_out=OUT_DIR            Generate JavaScript source.
--objc_out=OUT_DIR          Generate Objective C header and source.
--php_out=OUT_DIR           Generate PHP source file.
--python_out=OUT_DIR        Generate Python source file.
--ruby_out=OUT_DIR          Generate Ruby source file.

Go依赖包

在 go 语言中使用需要装相关的依赖包
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go

go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

类型对应关系

ptotoGoPHPJava说明
doublefloat64floatdouble
floatfloat32floatfloat
int32int32integerint负数效率低, 可使用sint32代替
int64int64integer/stringlong负数效率低, 可使用sint64代替
uint32uint32integerint[2]
uint64uint64integer/stringlong[2]
sint32int32integerint
sint64int64integer/stringlong
boolboolboolbool
stringstringstringString
bytes[]bytestringByteString

生成Protobuf

引入外部文件配置

IDE 取消自定义配置, 增加项目的proto目录路径

image-20220608175347062.png

目录说明

demo                                                    # demo目录, 生成命令执行位置
├─ api                                                 # proto编译目录
│  ├─ go                                            # Go-protobuf 生成目录
│    ├─ user
│     │   ├─ user.pb.go                     
│     │   └─ user_grpc.pb.go             
│     ├─ room
│         ├─ user.pb.go                     
│         └─ room_grpc.pb.go             
│
│  ├─ php                                            # PHP-protobuf 生成目录
│   
├─ client                                            # PHP-protobuf 生成目录
│  ├─ client.go                                # 客户端
│
├─ main                                             # 数据解析测试文件
│  ├─ main.go                                
│
├─ proto                                             # proto服务定义目录
│  ├─ user.proto                            # 定义用户服务proto
│  └─ room.proto                            # 定义房间服务proto
│
├─ server 
│  └─ server.go                                # 服务端

Proto协议文件

User.proto

// 定义proto版本
syntax = "proto3";

// 定义包名
package demo;
// 为go制定生成文件位置: 目录/包名
option go_package="grpc-demo/grpc-proto/api/go/user;userPb";
option php_namespace = "grpc\\user";
option php_metadata_namespace = "grpc\\userPb";


// 性别枚举类型
enum Gender {
  NOT_SET = 0;
  MALE = 1;
  FEMALE = 2;
}

// 定于用户对象
message User{
  string name = 1; // 第一个参数为名称
  string avatar = 2; // 第二个参数为头像
  Gender gender = 3; // 第三个性别
}

// 定义用户实体对象
message UserEntity{
  int32 id = 1; // 第一个参数为用户id
  User user = 2; // 第二个参数为用户对象
}

// 定义用户请求对象
message GetUserRequest {
  int32 id = 1; // 第一个请求参数为用户id
}

// 定义返回对象
message GetUserResponse {
  UserEntity user_entity = 1; // 第一个返回参数为用户实体
}

// 第一用户服务
service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
}

Room.Proto

syntax = "proto3";

package demo;
option go_package="grpc-demo/grpc-proto/api/go/room;roomPb";
option php_namespace = "grpc\\room";
option php_metadata_namespace = "grpc\\roomPb";

import "user.proto"; // 引入用户proto

message Room {
  int32 id = 1;
  string name = 2;
}

message RoomEntity {
  int32 id = 1;
  Room room = 2;
  UserEntity user =3; // 使用外部包类型
}

message GetRoomRequest {
  int32 id = 1;
}

message GetRoomResponse{
  RoomEntity room = 1;
}

service service{
  rpc GetRoom (GetRoomRequest) returns (GetRoomResponse);
}

编译

在 user.proto 同目录下执行命令

-I: 需要生成 proto 的源码目录文件 源码目录

--xxx_out: proto存放目录

# 编译 PHP
protoc -I ./proto user.proto --php_out=./api/php
protoc -I ./proto room.proto --php_out=./api/php

# 编译 Go
protoc -I=./proto user.proto --go_out api/go/user --go_opt paths=source_relative --go-grpc_out api/go/user --go-grpc_opt=paths=source_relative user.proto

protoc -I=./proto room.proto --go_out api/go/room --go_opt paths=source_relative --go-grpc_out api/go/room --go-grpc_opt=paths=source_relative room.proto

服务调用

数据解析

main/main.go

proto.Marshal: 结构体转换 proto 数据格式

proto.Marshal: proto 数据格式转换结构体

json.Marshal: 结构体转换 json

package main

import (
    "encoding/json"
    "fmt"
    "google.golang.org/protobuf/proto"
    roomPb "grpc-demo/grpc-proto/api/go/room"
)

func main() {
    req := roomPb.GetRoomRequest{
        Id: 1,
    }
  // 打印请求结构体: id:1
    fmt.Println(&req)

  // 结构体转成proto二进制流: 0801
    stream, _ := proto.Marshal(&req)
    fmt.Printf("%x\n", stream)

  // 二进制流转换成结构体: id:1
    var roomStruct roomPb.GetRoomRequest
    _ = proto.Unmarshal(stream, &roomStruct)
    fmt.Println(&roomStruct)

    // 结构体转成json {"id":1}
    jsonReq, _ := json.Marshal(req)
    fmt.Println(string(jsonReq))
}

服务端

server/server.go
package main

import (
    "context"
    "google.golang.org/grpc"
    roomPb "grpc-demo/grpc-proto/api/go/room"
    userPb "grpc-demo/grpc-proto/api/go/user"
    "math/rand"
    "net"
)

type Server struct {
    roomPb.UnimplementedServiceServer
}

func (s Server) GetRoom(ctx context.Context, request *roomPb.GetRoomRequest) (*roomPb.GetRoomResponse, error) {
    res := &roomPb.GetRoomResponse{
        Room: &roomPb.RoomEntity{
            Id: request.Id,
            Room: &roomPb.Room{
                Code: int32(rand.Intn(10)),
                Name: "room_" + string(request.Id),
            },
            // 用户集合
            User: []*userPb.UserEntity{
                {
                    Id: 1,
                    User: &userPb.User{
                        Name:   "二滑大魔王",
                        Avatar: "https://blog.qvbilam.xin",
                        Gender: userPb.Gender_MALE, // 枚举类型
                    },
                },
                {
                    Id: 2,
                    User: &userPb.User{
                        Name:   "Gyi",
                        Avatar: "https://blog.qvbilam.xin",
                        Gender: userPb.Gender_FEMALE,
                    },
                },
            },
        },
    }
    return res, nil
}

func main() {
    // 声明新的grpc服务
    GRPCServer := grpc.NewServer()
    // 注册用户服务
    roomPb.RegisterServiceServer(GRPCServer, &Server{})
    // 监听端口
    lis, err := net.Listen("tcp", "0.0.0.0:9101")
    if err != nil {
        panic(any(err))
    }
    // 启动服务
    err = GRPCServer.Serve(lis)
    if err != nil {
        panic(any(err))
    }
}

客户端

client/client.go
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    roomPb "grpc-demo/grpc-proto/api/go/room"
    "math/rand"
)

func main() {
    // 建立grpc连接
    conn, err := grpc.Dial("127.0.0.1:9101", grpc.WithInsecure())
    if err != nil {
        panic(any(err))
    }
    // 函数执行完后关闭连接
    defer func(connect *grpc.ClientConn) {
        _ = connect.Close()
    }(conn)

    // 实例化客户端
    client := roomPb.NewServiceClient(conn)
    // 调用函数
    userEntity, err := client.GetRoom(context.Background(), &roomPb.GetRoomRequest{
        Id: int32(rand.Intn(100)),
    })
    if err != nil {
        panic(any(err))
    }
    // 打印结果
    fmt.Println(userEntity)
  // room:{id:81  room:{code:7  name:"room_Q"}  user:{id:1  user:{name:"二滑大魔王"  avatar:"https://blog.qvbilam.xin"  gender:MALE}}  user:{id:2  user:{name:"Gyi"  avatar:"https://blog.qvbilam.xin"  gendeALE}}}
}
Last modification:June 23rd, 2022 at 01:18 pm