FlatBuffers Go gRPC Greeter 示例:从 .fbs 定义到可运行的服务端与客户端
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
本篇技术指南以仓库中 grpc/examples/go/greeter/README.md 为核心,完整讲解如何基于 FlatBuffers 构建一个 Go 语言的 gRPC 服务:从编写greeter.fbs的 RPC 服务定义,到生成 Go 模型代码与 gRPC 桩代码,再到启动服务端、调用客户端的完整流程。读完本文,你将掌握 FlatBuffers 作为 gRPC 序列化格式的接入方式、FlatbuffersCodec的编解码原理、一元 RPC(SayHello)与服务端流式 RPC(SayManyHellos)的完整实现细节,并可直接在本地复现运行。
示例整体结构与模块划分
示例采用三模块布局,与 README 中给出的目录结构一一对应:
grpc/examples/go/greeter/ ├── server # 服务端模块:监听端口、注册 Greeter 服务 ├── client # 客户端模块:构建 FlatBuffers 请求并调用 ├── models # FlatBuffers 模型代码与 gRPC 主代码(由 flatc 生成) └── README.mdserver/main.go:gRPC 服务端入口,注册 Greeter 服务并监听localhost:3000;client/main.go:gRPC 客户端入口,通过--name参数指定要发送的名字,依次调用SayHello与SayManyHellos;models/:由 FlatBuffers 编译器(flatc)生成的HelloRequest.go、HelloReply.go以及 gRPC Go 插件生成的Greeter_grpc.go,是服务端与客户端共享的公共代码。
三个模块各自拥有独立的go.mod:client与server都通过replace指令把models模块指向本地../models目录,例如 server/go.mod 中的:
replace github.com/google/flatbuffers/grpc/examples/go/greeter/models v0.0.0 => ../models这种方式避免了把生成的模型代码发布到远程仓库,本地开发时直接复用同一份models包,保证两端消息结构一致。
服务定义:greeter.fbs 中的 RPC 描述
整个示例的业务协议定义在 grpc/examples/greeter.fbs 中:
namespace models; table HelloReply { message:string; } table HelloRequest { name:string; } rpc_service Greeter { SayHello(HelloRequest):HelloReply; SayManyHellos(HelloRequest):HelloReply (streaming: "server"); }从中可以看到 FlatBuffers 描述 gRPC 服务的三个关键语法要素:
table定义消息体:HelloRequest携带一个name字符串字段,HelloReply携带一个message字符串字段,与常规 FlatBuffers schema 完全一致;rpc_service关键字:声明 gRPC 服务,本例服务名为Greeter;(streaming: "server")标注:SayHello是标准的一元 RPC(一次请求、一次响应);SayManyHellos声明为服务端流式 RPC,客户端发送一次请求,服务端可以连续推送多条HelloReply。
在 grpc/examples/greeter_v2.fbs 中还有该服务的第二版本定义(用于演示 Swift 等其他语言实现),Go 示例使用的是上述greeter.fbs。
生成模型代码:models 模块的产物
models目录下的三个文件是「生成代码 + 手写代码」协作的结果:
HelloRequest.go/HelloReply.go:由flatc --go生成,文件头标注 "Code generated by the FlatBuffers compiler. DO NOT EDIT.";Greeter_grpc.go:由 gRPC Go 插件生成,文件头标注 "Generated by gRPC Go plugin",其中RegisterGreeterServer、NewGreeterClient等正是服务端与客户端 main.go 直接调用的入口。
HelloRequest 的读写接口
以 models/HelloRequest.go 为例,它展示了 FlatBuffers 生成的典型 API 形态:
GetRootAsHelloRequest(buf, offset):从字节缓冲区反序列化出请求对象;HelloRequestStart(builder)/HelloRequestAddName(builder, name)/HelloRequestEnd(builder):使用flatbuffers.Builder从零构建一条请求;Name():以[]byte形式读取name字段,未设置时返回nil。
Greeter 服务的 gRPC 桩
models/Greeter_grpc.go 中与标准 gRPC 生成代码最大的不同在于消息类型:接口方法签名直接使用*flatbuffers.Builder作为请求/响应载体,而不是 protobuf 消息对象。例如:
type GreeterClient interface { SayHello(ctx context.Context, in *flatbuffers.Builder, opts ...grpc.CallOption) (*HelloReply, error) SayManyHellos(ctx context.Context, in *flatbuffers.Builder, opts ...grpc.CallOption) (Greeter_SayManyHellosClient, error) }服务端侧接口GreeterServer则反向使用*HelloRequest作为入参、*flatbuffers.Builder作为返回值:
type GreeterServer interface { SayHello(context.Context, *HelloRequest) (*flatbuffers.Builder, error) SayManyHellos(*HelloRequest, Greeter_SayManyHellosServer) error mustEmbedUnimplementedGreeterServer() }服务描述_Greeter_serviceDesc中注册了名为models.Greeter的服务:SayHello走一元MethodDesc,SayManyHellos走服务端流式StreamDesc(ServerStreams: true),这正是greeter.fbs中两种 RPC 形态在生成代码层面的落点。
运行服务端
按照 README 的步骤,在grpc/examples/go/greeter/server目录下执行:
cd server go clean go run main.go服务端启动后监听localhost:3000,其启动逻辑集中在 server/main.go 的main()中,核心只有三步:
- 监听 TCP 端口:
net.Listen("tcp", "localhost:3000"); - 注入 FlatBuffers 编解码器:
codec := &flatbuffers.FlatbuffersCodec{} grpcServer := grpc.NewServer(grpc.ForceServerCodec(codec))这一行是整个示例的关键:gRPC 默认使用 protobuf 编解码,而grpc.ForceServerCodec强制服务端改用flatbuffers.FlatbuffersCodec,从而让 gRPC 帧内直接承载 FlatBuffers 二进制,实现"零拷贝、无中间对象"的高效传输; 3.注册服务并启动:models.RegisterGreeterServer(grpcServer, newServer())后调用grpcServer.Serve(lis)。
SayHello:一元 RPC 的服务端实现
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 }实现细节值得注意:
- 通过
request.Name()读取请求中的名字,若字段为空则回退为"Unknown",体现 FlatBuffers 可选字段nil判断的惯用法; - 响应不构造中间对象,直接用
flatbuffers.NewBuilder(0)逐字段写入HelloReply并Finish,最终返回的是*flatbuffers.Builder而非序列化后的字节切片,由 gRPC 框架配合FlatbuffersCodec完成后续写帧。
SayManyHellos:服务端流式 RPC 的实现
for _, greeting := range greetings { // greetings = [...]string{"Hi", "Hallo", "Ciao"} idx := b.CreateString(greeting + " " + m) models.HelloReplyStart(b) models.HelloReplyAddMessage(b, idx) b.Finish(models.HelloReplyEnd(b)) if err := stream.Send(b); err != nil { return err } }服务端在单个请求内循环三次,通过stream.Send(b)连续推送三条问候消息(Hi、Hallo、Ciao),体现服务端流式 RPC 的语义:一次请求、多次响应,直到函数返回。
运行客户端
按照 README 的步骤,在grpc/examples/go/greeter/client目录下执行:
cd client go clean go run main.go --name NAME其中--name是客户端定义的命令行参数,默认值为"Flatbuffers",来自 client/main.go 中的:
name = flag.String("name", "Flatbuffers", "name to be sent to server :D")客户端连接与调用逻辑同样分为几步:
建立连接并指定编解码器
conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", 3000), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultCallOptions(grpc.ForceCodec(flatbuffers.FlatbuffersCodec{})))- 服务端未配置 TLS,因此客户端必须使用
insecure.NewCredentials(),否则握手会失败; grpc.WithDefaultCallOptions(grpc.ForceCodec(...))与grpc.CallContentSubtype("flatbuffers")共同作用,确保所有调用的请求/响应都按 FlatBuffers 编解码。
构建请求并调用 SayHello
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")) log.Printf("server said %q", request.Message())请求的构建与服务端一致:先CreateString创建字符串偏移,再HelloRequestStart→HelloRequestAddName→HelloRequestEnd→Finish。调用时设置 10 秒超时上下文,并把*flatbuffers.Builder直接作为请求参数传入。响应*HelloReply通过request.Message()读取服务端回写的问候语。
消费服务端流 SayManyHellos
stream, err := client.SayManyHellos(ctx, b, grpc.CallContentSubtype("flatbuffers")) for { request, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatalf(...) } log.Printf("server said %q", request.Message()) }客户端循环调用stream.Recv()逐条接收服务端推送的消息,直到收到io.EOF才结束——这是 gRPC Go 服务端流式客户端收包的标准写法。按默认参数运行,客户端将依次收到三条问候:
server said "welcome Flatbuffers" server said "Hi Flatbuffers" server said "Hallo Flatbuffers" server said "Ciao Flatbuffers"依赖与 Go 版本要求
三个模块的go.mod均声明go 1.15,核心依赖为:
github.com/google/flatbuffers v2.0.8+incompatible:FlatBuffers Go 运行时库,提供flatbuffers.Builder、FlatbuffersCodec等;google.golang.org/grpc v1.56.3:gRPC Go 框架。
client、server模块额外通过replace指令引用本地models模块。由于示例本身位于 FlatBuffers 仓库内,直接go run时 Go 模块解析会命中仓库自身的依赖;若将其复制到仓库外使用,需要把github.com/google/flatbuffers替换为你实际引入的 FlatBuffers 版本路径。
从零复现:自己动手生成模型代码
仓库中已提交生成好的models代码,但如果你想从greeter.fbs重新生成(例如修改了 schema),流程是:
- 先构建
flatc(FlatBuffers 编译器),参见 docs/building.md 中的构建说明; - 在 grpc/examples 目录下执行
flatc --go --grpc -o go/greeter/models greeter.fbs; - 生成的
HelloRequest.go、HelloReply.go与Greeter_grpc.go会写入models目录,其中 gRPC 桩需要--grpc选项并链接 gRPC Go 插件。
仓库还提供了批量重新生成的辅助脚本,可参考 scripts/generate_grpc_examples.py 与 grpc/examples/go/format.sh,后者用于对生成代码执行gofmt格式化。
与仓库其他语言示例的对照
同一份greeter.fbs在仓库中被翻译成了多种语言的完整示例,可作为学习参照:
- Python:见 grpc/examples/python/greeter/README.md,包含
server.py/client.py; - Swift:见 grpc/examples/swift/Greeter/README.md;
- TypeScript:见 grpc/examples/ts/greeter/README.md。
gRPC 的框架级测试则集中在 grpc/tests 目录,其中 grpc/tests/grpctest.cpp 与 grpc/tests/grpctest.py 对FlatbuffersCodec在 C++ 与 Python 侧的接入做了系统性验证,可作为深入理解编解码器实现与边界行为的补充材料。
小结
这个 Go Greeter 示例浓缩了 FlatBuffers 与 gRPC 集成的全部关键点:用rpc_service在.fbs中声明服务、用flatc --grpc生成 Go 桩代码、在服务端与客户端两侧通过FlatbuffersCodec替换默认 protobuf 编解码,并分别演示了一元 RPC 与服务端流式 RPC 的完整读写流程。理解它之后,把现有 protobuf gRPC 服务迁移到 FlatBuffers 序列化,或在新的 Go 服务中采用零拷贝传输方案,都可以直接照搬这套结构。
【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考