介绍
gRPC默认应用Protocol Buffers编码,同时也反对其余编码如:JSON、FlatBuffers等。
FlatBuffers是一个跨平台的序列化库,旨在实现最大的内存效率。它容许您间接拜访序列化数据,而无需首先对其进行解析/解包,同时仍具备良好的向前/向后兼容性。
我的项目地址:https://github.com/google/flatbuffers
FlatBuffers在解编码性能上要比Protocol Buffers快很多,这里有两篇具体介绍Protocol Buffers和FlatBuffers比照的文章:
https://blog.csdn.net/chosen0ne/article/details/43033575
https://juzii.gitee.io/2020/03/02/protobuf-vs-flatbuffer/
这里有一篇文章具体介绍了FlatBuffers以及schema的编写:
https://halfrost.com/flatbuffers_schema/
这里次要来演示一下如何在gRPC中应用FlatBuffers.
编码
编写fbs接口定义文件
api/fbs/greeter.fbs
namespace models;table HelloReply { message:string;}table HelloRequest { name:string;}table ManyHellosRequest { name:string; num_greetings:int;}rpc_service Greeter { SayHello(HelloRequest):HelloReply; SayManyHellos(ManyHellosRequest):HelloReply (streaming: "server");}
这里的定义和protobuf差不多,应用table
定义构造体,应用rcp_service
定义接口。
这里定义了三个构造体用于数据发送和接管,定义了两个接口用于演示。
生成gRPC代码
先装置flatc
从上面地址中下载对应版本的flatc即可
https://github.com/google/flatbuffers/releases/tag/v2.0.0
flatc --go --grpc -o api/ api/fbs/greeter.fbs
参数阐明:
--go 指定生成的语言是go
--grpc 指定生成grpc代码
-o 可选,指定要生成的指标文件目录前缀
--go-namespace 可选,指定生成的包名,笼罩 fbs 文件中的定义
会在指定目录下生成一个models
目录,外面即是生成的代码,这个目录名就是fbs
文件中定义的namespace
,也能够通过参数'--go-namespace
来笼罩这个值,以指定新的目录,如:
flatc --go --go-namespace newmodels --grpc -o api/ api/fbs/greeter.fbs
倡议通过fbs
定义namespace
,这个namespace
也是Go文件的package
名称。
生成的文件目录是这样的:
├── api│ ├── fbs│ │ └── greeter.fbs│ └── models│ ├── Greeter_grpc.go│ ├── HelloReply.go│ ├── HelloRequest.go│ └── ManyHellosRequest.go
当初咱们能够编写gRPC的代码了。
初始化go mod
在我的项目根目录执行:
go mod init github.com/safeie/grpc-flatbuffers-examplego mod tidy
编写gRPC服务端
cmd/server/main.go
package mainimport ( "context" "fmt" "log" "net" "github.com/safeie/grpc-flatbuffers-example/api/models" "google.golang.org/grpc" flatbuffers "github.com/google/flatbuffers/go")var ( greetings = [...]string{"Hi", "Hallo", "Ciao"})type greeterServer struct { models.UnimplementedGreeterServer}func (s *greeterServer) SayHello(ctx context.Context, request *models.HelloRequest) (*flatbuffers.Builder, error) { v := request.Name() var m string if v == nil { m = "Unknown" } else { m = string(v) } b := flatbuffers.NewBuilder(0) idx := b.CreateString("Welcome " + m) models.HelloReplyStart(b) models.HelloReplyAddMessage(b, idx) b.Finish(models.HelloReplyEnd(b)) return b, nil}func (s *greeterServer) SayManyHellos(request *models.ManyHellosRequest, stream models.Greeter_SayManyHellosServer) error { v := request.Name() var m string if v == nil { m = "Unknown" } else { m = string(v) } num := request.NumGreetings() b := flatbuffers.NewBuilder(0) for _, greeting := range greetings { idx := b.CreateString(fmt.Sprintf("%s %s ,num %d", greeting, m, num)) models.HelloReplyStart(b) models.HelloReplyAddMessage(b, idx) b.Finish(models.HelloReplyEnd(b)) if err := stream.Send(b); err != nil { return err } } return nil}func newServer() *greeterServer { return &greeterServer{}}func main() { lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", 3000)) if err != nil { log.Fatalf("Falied to listen: %v", err) } codec := &flatbuffers.FlatbuffersCodec{} grpcServer := grpc.NewServer(grpc.ForceServerCodec(codec)) models.RegisterGreeterServer(grpcServer, newServer()) if err := grpcServer.Serve(lis); err != nil { fmt.Println(err) panic(err) }}
能够 go build
测试,如果有依赖问题,回到根目录,执行 go mod tidy
下载依赖就能够了。
编写gRPC客户端
cmd/client/main.go
package mainimport ( "context" "flag" "fmt" "io" "log" "math/rand" "time" flatbuffers "github.com/google/flatbuffers/go" "github.com/safeie/grpc-flatbuffers-example/api/models" "google.golang.org/grpc")var ( addr = "3000" name = flag.String("name", "Flatbuffers", "name to be sent go server :D"))func printSayHello(client models.GreeterClient, name string) { log.Printf("Name to be sent (%s)", name) b := flatbuffers.NewBuilder(0) i := b.CreateString(name) models.HelloRequestStart(b) models.HelloRequestAddName(b, i) b.Finish(models.HelloRequestEnd(b)) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() request, err := client.SayHello(ctx, b, grpc.CallContentSubtype("flatbuffers")) if err != nil { log.Fatalf("%v.SayHello(_) = _, %v: ", client, err) } log.Printf("server said %q", request.Message())}func printSayManyHello(client models.GreeterClient, name string, num int32) { log.Printf("Name to be sent (%s), num to be sent (%d)", name, num) b := flatbuffers.NewBuilder(0) i := b.CreateString(name) models.ManyHellosRequestStart(b) models.ManyHellosRequestAddName(b, i) models.ManyHellosRequestAddNumGreetings(b, num) b.Finish(models.ManyHellosRequestEnd(b)) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() stream, err := client.SayManyHellos(ctx, b, grpc.CallContentSubtype("flatbuffers")) if err != nil { log.Fatalf("%v.SayManyHellos(_) = _, %v", client, err) } for { request, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatalf("%v.SayManyHellos(_) = _, %v", client, err) } log.Printf("server said %q", request.Message()) }}func main() { flag.Parse() conn, err := grpc.Dial(fmt.Sprintf("localhost:%s", addr), grpc.WithInsecure(), grpc.WithCodec(flatbuffers.FlatbuffersCodec{})) if err != nil { log.Fatalf("Failed to dial: %v", err) } defer conn.Close() client := models.NewGreeterClient(conn) printSayHello(client, *name) num := rand.Int31() printSayManyHello(client, *name, num)}
能够 go build
测试,如果有依赖问题,回到根目录,执行 go mod tidy
下载依赖就能够了。
运行测试
开一个命令行窗口运行:cd cmd/server && go run main.go
开一个命令行窗口运行:cd cmd/client && go run main.go
输入后果应该是这样的:
2021/12/15 18:04:16 Name to be sent (Flatbuffers)2021/12/15 18:04:16 server said "Welcome Flatbuffers"2021/12/15 18:04:16 Name to be sent (Flatbuffers), num to be sent (1298498081)2021/12/15 18:04:16 server said "Hi Flatbuffers ,num 1298498081"2021/12/15 18:04:16 server said "Hallo Flatbuffers ,num 1298498081"2021/12/15 18:04:16 server said "Ciao Flatbuffers ,num 1298498081"
实现的示例我的项目代码:https://github.com/safeie/grpc-flatbuffers-example
参考链接:
https://github.com/google/flatbuffers/tree/master/grpc/examples/go/greeter