1

introduce

gRPC uses Protocol Buffers encoding by default, and also supports other encodings such as JSON, FlatBuffers, etc.

FlatBuffers is a cross-platform serialization library designed to achieve maximum memory efficiency. It allows you to directly access serialized data without having to parse/unpack it first, while still having good forward/backward compatibility.

Project address: https://github.com/google/flatbuffers

FlatBuffers is much faster than Protocol Buffers in decoding performance. Here are two articles detailing the comparison between Protocol Buffers and FlatBuffers:

https://blog.csdn.net/chosen0ne/article/details/43033575
https://juzii.gitee.io/2020/03/02/protobuf-vs-flatbuffer/

Here is an article that details the writing of FlatBuffers and schema:

https://halfrost.com/flatbuffers_schema/

Here is mainly to demonstrate how to use FlatBuffers in gRPC.

coding

Write fbs interface definition file

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");
}

The definition here is similar to protobuf, using table define the structure and rcp_service define the interface.

Three structures are defined here for data sending and receiving, and two interfaces are defined for demonstration.

Generate gRPC code

First install flatc and download the corresponding version of flatc from the address below.

https://github.com/google/flatbuffers/releases/tag/v2.0.0

flatc --go --grpc -o api/ api/fbs/greeter.fbs

Parameter Description:

--go specifies that the generated language is go
--grpc specifies to generate grpc code
-o optional, specify the directory prefix of the target file to be generated
--go-namespace is optional, specify the generated package name, overwrite the definition in the fbs file

models directory will be generated in the specified directory, which is the generated code. The directory name is fbs defined in the namespace file. You can also '--go-namespace to specify a new directory, such as:

flatc --go --go-namespace newmodels --grpc -o api/ api/fbs/greeter.fbs

Recommended by fbs defined namespace , this namespace is Go file package name.

The generated file directory looks like this:

├── api
│   ├── fbs
│   │   └── greeter.fbs
│   └── models
│       ├── Greeter_grpc.go
│       ├── HelloReply.go
│       ├── HelloRequest.go
│       └── ManyHellosRequest.go

Now we can write gRPC code.

Initialize go mod

Execute in the project root directory:

go mod init github.com/safeie/grpc-flatbuffers-example
go mod tidy

Write gRPC server

cmd/server/main.go

package main

import (
    "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)
    }
}

You can go build . If there is a dependency problem, go back to the root directory and execute go mod tidy download the dependency.

Write gRPC client

cmd/client/main.go

package main

import (
    "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)
}

You can go build . If there is a dependency problem, go back to the root directory and execute go mod tidy download the dependency.

Run test

Open a command line window to run: cd cmd/server && go run main.go

Open a command line window to run: cd cmd/client && go run main.go

The output should look like this:

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"

Completed example project code: https://github.com/safeie/grpc-flatbuffers-example

Reference link:

https://github.com/google/flatbuffers/tree/master/grpc/examples/go/greeter


hengfeiyang
1.7k 声望290 粉丝