概念
protoBuffer 简称 protobuf,是Google推出的一种新型数据存储格式,压缩性好,传输速度快。序列化反序列话速度快。性能强于json, xml,支持多种语言。
protobuf中所有的字段都是可选的,没种类型都有相应的默认值。定义的参数不传值也是有默认值的,例如枚举默认值为0
环境安装
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
类型对应关系
ptoto | Go | PHP | Java | 说明 |
---|---|---|---|---|
double | float64 | float | double | |
float | float32 | float | float | |
int32 | int32 | integer | int | 负数效率低, 可使用sint32代替 |
int64 | int64 | integer/string | long | 负数效率低, 可使用sint64代替 |
uint32 | uint32 | integer | int[2] | |
uint64 | uint64 | integer/string | long[2] | |
sint32 | int32 | integer | int | |
sint64 | int64 | integer/string | long | |
bool | bool | bool | bool | |
string | string | string | String | |
bytes | []byte | string | ByteString |
生成Protobuf
引入外部文件配置
IDE 取消自定义配置, 增加项目的proto目录路径
目录说明
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.goproto.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}}}
}
代码块的样式乱了