|
protocol buffer介绍
protobuf是google开源的数据交换格式,它有两个必要组成部分:
- protocol compiler:协议编译器,用来编译.proto文件,它是用c++写的,可以直接下载安装二进制使用,或者基于源码编译,由于是开源的,也可以基于代码进行修改和扩展。【代码库】中有更详细的介绍。
- protobuf运行时:protobuf支持多个语言,可以在下列相应的代码库中找到安装方法。
关于go protobuf运行时
go运行时包含两个主要版本,第二个版本提供更简单的API,支持protobuf反射和很多提升,推荐新的代码使用新版本。
- 第一个主要版本由github.com/golang/protobuf模块实现,具体介绍可查看前面链接中的官方文档。
- 第二个主要版本有google.golang.org/protobuf模块实现,提供了将prootbuf文件生成Go代码和Go语言序列化消息的运行时实现。它由两部分组成:
- Code Gennerator:代码生成器protoc-gen-go是用来将.proto编译生成Go代码的protoc插件,它作为参数传递给protoc,具体方法参考官方文档。新版本的模块不支持service类型,需要单独安装grpc-go插件,具体方法参考官方文档。安装命令如下,Make sure that your $GOBIN is in your $PATH
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
- Runtime library:protobuf模块包含一组Go包,这些包构成了protobuf在Go中的运行时实现,提供了一组定义消息的接口和消息序列化的函数(如json,text等)。可以在官方文档中找到这些包。
go protobuf实例
完整代码查看:github
protobuf文件
syntax = "proto3";
package person;
option go_package="./person";
enum Sex{
MAN = 0;
WOMAN = 1;
}
message Person{
string name = 1;
int32 age = 2;
Sex sex = 3;
}
编译命令
protoc -I./pb --go_out=./person --go_opt=paths=source_relative ./pb/person.proto使用
package main
import (
"fmt"
pb "com.example.test/person"
"google.golang.org/protobuf/proto"
)
func main() {
p := pb.Person{
Name: "jone",
Age: 18,
Sex: pb.Sex_MAN,
}
fmt.Println("p : ", p.String())
fmt.Println("p : ", "name:", p.GetName(), "age:", p.GetAge(), "sex:", p.GetSex())
// 使用 google.golang.org/protobuf/proto 序列化和反序列化
bytes, err := proto.Marshal(&p)
if err != nil {
panic(err)
}
var p2 pb.Person
err = proto.Unmarshal(bytes, &p2)
fmt.Println("p2 : ", "name:", p2.GetName(), "age:", p2.GetAge(), "sex:", p2.GetSex())
}
跨语言能力
protobuf支持在多个语言之间交换数据。其中C++ /C# /JAVA /Kotlin /Objective-C /PHP /Python /Ruby由protocol buffer编译器protoc直接支持。Dart /Go由google提供,以插件方式提供给protoc。
其他语言不直接支持,但是可以使用第三方github项目。
grpc介绍
官方文档
RPC即远程过程调用(Remote procedure call),它的客户端程序可以像在本地一样直接调用一个服务端的方法,这使得创建分布式应用和服务变得很简单。
grpc是由google开源的一个高性能RPC框架,它使用
protobuf语言作为IDL(接口定义语言,Interface define language)和底层数据交换格式。与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。在服务端,服务端实现这个接口并运行一个 gRPC 服务器来处理客户端调用。在客户端,客户端有一个存根(在某些语言中仅称为客户端),它提供与服务器相同的方法。
gRPC 客户端和服务器可以在各种环境中运行和相互通信,例如,你可以使用 Go、Python 或 Ruby 中的客户端轻松地在 Java 中创建 gRPC 服务器。后续主要介绍Go语言使用grpc。
Go语言grpc
新版本的protoc不直接支持编译Go语言,需要单独安装插件,具体方法参考官方文档。
安装插件
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
# 编译命令
$ protoc -I. --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
编译命令参数
参数 | 说明 | --go_out=. | 指定go语言的protobuf编译结果输出路径为 ./,即xxx.pb.go的路径,加这个参数将加载protoc-gen-go插件 | --go_opt=paths=source_relative | go_out路径为相对路径 | --go-grpc_out=. | 指定go语言的protobuf serivce输出路径为./,即xxx_grpc.pb.go的路径,加这个参数将加载protoc-gen-go-grpc插件。 | --go-grpc_opt=paths=source_relative | go-grpc_out路径为相对路径 | -I. 或--proto_path=./ | 指定依赖的.proto搜索路径,与gcc编译器的-I类似,若不指定,则默认为当前目录 | helloworld/helloworld.proto | 最后加上需要编译的文件名 | 生成的文件
- xxx.pb.go:包含用于填充、序列化和检索请求和响应消息类型的所有协议缓冲区代码。
- xxx_grpc.pb.go:包含
- proto文件service下定义的客户端可调用的方法的interface类型(或叫存根stub)。
- proto文件service下定义的服务器需要实现的接口类型。
实例
实例用一个基本的操作演示了grpc的四种消息类型,一元消息,c->s流,s->c流,c<->s双向流,完整代码查看:github
protobuf
syntax = &#34;proto3&#34;;
option go_package=&#34;./pb&#34;;
import &#34;google/protobuf/timestamp.proto&#34;;
enum EmployeeStatus{
NORMAL = 0;
NO_VACATION = 1;
RESINGED = 2;
RETIRED = 3;
}
message Employee{
int32 id = 1;
int32 no = 2;
string name = 3;
// float salary = 4;
MonthSalary monthSalary = 5;
EmployeeStatus status = 6;
google.protobuf.Timestamp lastModified = 7;
reserved 4;
reserved &#34;salary&#34;;
}
message MonthSalary{
float basic = 1;
float bonus = 2;
}
message GetByNoRequest{
int32 no = 1;
}
message EmployeeResponse{
Employee employee = 1;
}
message GetAllRequest{}
message AddPhotoRequest{
bytes data = 1;
}
message AddPhotoResponse{
bool isOk = 1;
}
message SaveEmployeeRequest{
Employee employee = 1;
}
service EmployeeService{
// 一元消息
rpc GetByNo(GetByNoRequest) returns (EmployeeResponse);
// server stream
// 获取所有员工
rpc GetAll(GetAllRequest) returns (stream EmployeeResponse);
// client stream
// 上传头像信息
rpc AllPhoto(stream AddPhotoRequest) returns (AddPhotoResponse);
// 双向stream
// 上传employee信息
rpc Save(SaveEmployeeRequest) returns (EmployeeResponse);
rpc SaveAll(stream SaveEmployeeRequest) returns (stream EmployeeResponse);
}
编译命令
protoc -I. \
--go_out=../server/pb \
--go_out=../client/pb \
--go_opt=paths=source_relative \
--go-grpc_out=../server/pb \
--go-grpc_out=../client/pb \
--go-grpc_opt=paths=source_relative \
messages.proto
client
package main
import (
&#34;context&#34;
&#34;fmt&#34;
&#34;io&#34;
&#34;log&#34;
&#34;os&#34;
&#34;time&#34;
&#34;github.com/lxxxxxxx/grpc-client/pb&#34;
&#34;google.golang.org/grpc&#34;
&#34;google.golang.org/grpc/credentials&#34;
&#34;google.golang.org/grpc/metadata&#34;
&#34;google.golang.org/protobuf/types/known/timestamppb&#34;
)
const port = &#34;:5001&#34;
func main() {
creds, err := credentials.NewClientTLSFromFile(&#34;./keys/server.pem&#34;, &#34;127.0.0.1&#34;)
if err != nil {
log.Fatalln(err.Error())
}
options := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
conn, err := grpc.Dial(&#34;localhost&#34;+port, options...)
if err != nil {
log.Fatalln(err.Error())
}
defer conn.Close()
// 和server不同的是,grpc会实现client的接口
// 我们只需要调用这些接口取数据就可以
client := pb.NewEmployeeServiceClient(conn)
getByNo(client)
getAll(client)
addPhoto(client)
saveAll(client)
}
func getByNo(client pb.EmployeeServiceClient) {
res, err := client.GetByNo(context.Background(), &pb.GetByNoRequest{No: 1999})
if err != nil {
log.Fatalln(err.Error())
}
fmt.Println(res.Employee)
}
func getAll(client pb.EmployeeServiceClient) {
stream, err := client.GetAll(context.Background(), &pb.GetAllRequest{})
if err != nil {
log.Fatalln(err.Error())
}
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalln(err.Error())
}
fmt.Println(res.Employee)
}
}
func addPhoto(client pb.EmployeeServiceClient) {
imgFile, err := os.Open(&#34;avatar.jpeg&#34;)
if err != nil {
log.Fatalln(err.Error())
}
defer imgFile.Close()
md := metadata.New(map[string]string{&#34;no&#34;: &#34;1996&#34;})
context := context.Background()
context = metadata.NewOutgoingContext(context, md)
stream, err := client.AllPhoto(context)
if err != nil {
log.Fatalln(err.Error())
}
for {
chunk := make([]byte, 32*1024)
chunkSize, err := imgFile.Read(chunk)
if err == io.EOF {
break
}
if err != nil {
log.Fatalln(&#34;--- &#34; + err.Error())
}
if chunkSize < len(chunk) {
chunk = chunk[:chunkSize]
}
stream.Send(&pb.AddPhotoRequest{Data: chunk})
}
res, err := stream.CloseAndRecv()
if err != nil {
log.Fatalln(&#34;--- 1&#34; + err.Error())
}
fmt.Println(&#34;add photo isok:&#34;, res.IsOk)
}
func saveAll(client pb.EmployeeServiceClient) {
employees := []pb.Employee{
{
Id: 1,
No: 23,
Name: &#34;lx&#34;,
MonthSalary: &pb.MonthSalary{
Basic: 200.0,
Bonus: 23233.0,
},
Status: pb.EmployeeStatus_NORMAL,
LastModified: &timestamppb.Timestamp{
Seconds: time.Now().Unix(),
},
},
{
Id: 2,
No: 23,
Name: &#34;wzz&#34;,
MonthSalary: &pb.MonthSalary{
Basic: 200.0,
Bonus: 23233.0,
},
Status: pb.EmployeeStatus_NORMAL,
LastModified: &timestamppb.Timestamp{
Seconds: time.Now().Unix(),
},
},
}
stream, err := client.SaveAll(context.Background())
if err != nil {
log.Fatalln(err.Error())
}
finishChannel := make(chan struct{})
go func() {
for {
res, err := stream.Recv()
if err == io.EOF {
finishChannel <- struct{}{}
break
}
if err != nil {
log.Fatalln(err.Error())
}
fmt.Println(res.Employee)
}
}()
for _, e := range employees {
err := stream.Send(&pb.SaveEmployeeRequest{Employee: &e})
if err != nil {
log.Fatalln(err.Error())
}
}
stream.CloseSend()
<-finishChannel
}
server
package main
// https://www.bilibili.com/video/BV1eE411T7GC?p=1
import (
&#34;context&#34;
&#34;errors&#34;
&#34;fmt&#34;
&#34;io&#34;
&#34;log&#34;
&#34;net&#34;
&#34;time&#34;
&#34;github.com/lxxxxxxxx/grpc-server/pb&#34;
&#34;google.golang.org/grpc&#34;
&#34;google.golang.org/grpc/credentials&#34;
&#34;google.golang.org/grpc/metadata&#34;
&#34;google.golang.org/protobuf/types/known/timestamppb&#34;
)
const port = &#34;:5001&#34;
var employees = []pb.Employee{
{
Id: 1,
No: 1999,
Name: &#34;Dave&#34;,
MonthSalary: &pb.MonthSalary{
Basic: 5000,
Bonus: 1000,
},
Status: pb.EmployeeStatus_NORMAL,
LastModified: &timestamppb.Timestamp{
Seconds: time.Now().Unix(),
},
},
{
Id: 2,
No: 1996,
Name: &#34;Lili&#34;,
MonthSalary: &pb.MonthSalary{
Basic: 6000,
Bonus: 500,
},
Status: pb.EmployeeStatus_NORMAL,
LastModified: &timestamppb.Timestamp{
Seconds: time.Now().Unix(),
},
},
}
func main() {
listen, err := net.Listen(&#34;tcp&#34;, port)
if err != nil {
log.Fatalln(err.Error())
}
creds, err := credentials.NewServerTLSFromFile(&#34;./keys/server.pem&#34;, &#34;./keys/ca.key&#34;)
if err != nil {
log.Fatalln(err.Error())
}
options := []grpc.ServerOption{grpc.Creds(creds)}
server := grpc.NewServer(options...)
pb.RegisterEmployeeServiceServer(server, new(employeeService))
log.Println(&#34;grpc server listening &#34; + port)
server.Serve(listen)
}
type employeeService struct {
pb.UnimplementedEmployeeServiceServer
}
func (*employeeService) GetByNo(ctx context.Context, req *pb.GetByNoRequest) (*pb.EmployeeResponse, error) {
for _, e := range employees {
if e.No == req.No {
fmt.Println(&#34;find employee no:&#34;, e.No)
return &pb.EmployeeResponse{Employee: &e}, nil
}
}
return nil, errors.New(&#34;employee not found&#34;)
}
func (*employeeService) GetAll(req *pb.GetAllRequest, stream pb.EmployeeService_GetAllServer) error {
for _, e := range employees {
stream.Send(&pb.EmployeeResponse{
Employee: &e,
})
time.Sleep(time.Second)
}
return nil
}
func (*employeeService) AllPhoto(stream pb.EmployeeService_AllPhotoServer) error {
md, ok := metadata.FromIncomingContext(stream.Context())
if ok {
fmt.Printf(&#34;Employee no:%s \n&#34;, md[&#34;no&#34;][0])
}
img := []byte{}
for {
data, err := stream.Recv()
if err == io.EOF {
fmt.Printf(&#34;img size: %d\n&#34;, len(img))
stream.SendAndClose(&pb.AddPhotoResponse{IsOk: true})
return nil
}
if err != nil {
log.Fatalln(err.Error())
return err
}
fmt.Printf(&#34;recv size:%d \n&#34;, len(data.Data))
img = append(img, data.Data...)
time.Sleep(time.Millisecond * 500)
}
return errors.New(&#34;add photo failed.&#34;)
}
func (*employeeService) Save(ctx context.Context, req *pb.SaveEmployeeRequest) (*pb.EmployeeResponse, error) {
for _, e := range employees {
if e.No == req.Employee.No {
fmt.Println(&#34;employee exist,no:&#34;, e.No)
return &pb.EmployeeResponse{
Employee: req.Employee,
}, nil
}
}
employees = append(employees, *req.Employee)
return &pb.EmployeeResponse{
Employee: req.Employee,
}, nil
}
func (*employeeService) SaveAll(stream pb.EmployeeService_SaveAllServer) error {
for {
empReq, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalln(err.Error())
return err
}
employees = append(employees, *empReq.Employee)
stream.Send(&pb.EmployeeResponse{Employee: empReq.Employee})
}
for _, e := range employees {
fmt.Println(e)
}
return nil
}
grpc-gateway介绍
gRPC-Gateway 是 protoc 的插件。它读取gRPC服务定义并生成反向代理服务器,将 RESTful JSON API 转换为 gRPC。此服务器是根据您的 gRPC 定义中的自定义选项生成的。
详细请参考官方文档。
gRPC-Gateway 可帮助您同时提供 gRPC 和 RESTful 风格的API。
安装插件
使用如下命令安装protoc插件,安装之后将在$GOPATH/bin目录下会有四个工具
protoc-gen-grpc-gateway
protoc-gen-openapiv2
protoc-gen-go
protoc-gen-go-grpc其中protoc-gen-go用于编译go语言版本的protobuf,protoc-gen-go-grpc用于编译go语言版本的grpc service,这两个工具与上面的介绍中安装的是一样的。protoc-gen-grpc-gateway、protoc-gen-openapiv2两个工具才是真正与grpc-gateway相关的插件。
$ go install \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc
未完待续... |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|