【免费下载链接】grpcurl
Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers
项目地址:
https://gitcode.com/gh_mirrors/gr/grpcurl
点击查看 免费下载
bankdemo 是 grpcurl 仓库内置的一个示例 gRPC 服务器,曾在 GopherCon 2018 上用于现场演示 grpcurl 的各项能力。本文以 bankdemo 官方说明 为骨架,结合其源码(main.go、bank.go、chat.go、db.go、auth.go)逐层拆解:你将掌握如何用 go run 一键启动它、如何利用 server reflection 让 grpcurl 免 proto 文件直接探索与调用其服务、如何通过 authorization 请求头模拟"客户/客服"两种身份、以及如何用 grpcurl 实操一元 RPC、服务端流和全双工双向流三种调用模式。
bankdemo 是什么:为演示 grpcurl 而生的 gRPC 服务器
按 README 的定位,bankdemo 是一个示例性 gRPC 服务器,专门用来演示 grpcurl。它演示了构建 gRPC 服务端时几个非常有意思的概念,其中最具代表性的是聊天功能——依赖全双工双向流(full-duplex bidirectional streams)。文档同时明确提醒:这段代码是为"有趣、具体"的演示而写的,不应被视为任何意义上的生产级代码,阅读时请关注其教学价值而非工程完备性。
它演示的核心特性可归纳为三条:
- 纯内存数据模型:账户、交易、余额全部保存在内存中,不依赖外部数据库;
- 周期性 + 优雅退出双重持久化:每几秒、以及在收到 SIGTERM/SIGINT 优雅关闭时,把状态写入 accounts.json,进程重启后可恢复;
- 聊天会话追踪:除银行账户数据外,服务端还维护"聊天会话",用于演示客户与客服之间基于双向流实现的实时对话应用。
仓库内还有另一个更朴素的测试服务器 testserver(见 testserver/README.md),其 RPC 接口定义在 example.proto 中,用于日常单元测试;而文档建议"想要更有趣的测试服务器,请看 bankdemo"——因为它提供了更具体、更贴近真实业务的 RPC 接口,包括全双工双向流方法。因此 bankdemo 是仓库内学习 grpcurl 与 gRPC 服务端交互的最佳实操样本。
快速启动 bankdemo
bankdemo 没有提供独立的 shell 脚本,直接在仓库根目录用 Go 运行即可:
# 在 grpcurl 仓库根目录执行
go run ./internal/testing/cmd/bankdemo
启动时支持两个命令行参数,定义在 main.go:
| -port | 12345 | bankdemo gRPC 服务监听的端口 |
| -datafile | accounts.json | 银行账户数据保存/加载的文件路径与文件名 |
例如换端口并指定数据文件:
go run ./internal/testing/cmd/bankdemo -port 54321 -datafile ./mybank.json
启动流程(main.go)依次为:加载既有数据文件 → 在 127.0.0.1:<port> 上监听 TCP → 创建 gRPC 服务器并注册 Bank、Support 两个服务 → 启动后台定时保存协程 → 注册 server reflection → 等待并处理 SIGINT/SIGTERM。启动成功后会在日志中输出 server starting, listening on 127.0.0.1:12345 之类的信息。
值得注意的细节:
- 默认只绑定回环地址 127.0.0.1,即仅本机可访问,适合本地演示;
- 服务端代码通过 //go:generate protoc –go_out=. –go-grpc_out=. bank.proto support.proto(main.go)声明了 .proto 的代码生成方式,生成的桩代码见 bank.pb.go、bank_grpc.pb.go、support.pb.go、support_grpc.pb.go;
- 最关键的一点:main 中显式调用了 reflection.Register(grpcSvr)(main.go)。这意味着 grpcurl 无需任何 .proto 源文件或 protoset 文件,仅靠 server reflection 就能列出服务、查看 schema 并动态调用所有方法。
用 grpcurl 探索 bankdemo 的 Schema
由于服务端开启了 reflection,最简单的探索方式就是直接连过去。注意 bankdemo 走的是明文 HTTP/2(无 TLS),因此所有 grpcurl 命令都需要 -plaintext 标志。
列出服务器暴露的所有服务:
grpcurl -plaintext 127.0.0.1:12345 list
预期输出包含 Bank 与 Support 两个服务(以及 reflection 相关的 grpc.reflection.v1alpha.ServerReflection 等)。
查看某个服务的全部方法:
grpcurl -plaintext 127.0.0.1:12345 list Bank
grpcurl -plaintext 127.0.0.1:12345 list Support
查看具体符号的定义(grpcurl 会以等价的 proto 源片段形式打印描述):
grpcurl -plaintext 127.0.0.1:12345 describe Bank
grpcurl -plaintext 127.0.0.1:12345 describe Bank.GetTransactions
grpcurl -plaintext 127.0.0.1:12345 describe Support.ChatCustomer
grpcurl -plaintext 127.0.0.1:12345 describe bankdemo.Account
这些能力来自 grpcurl 对 server reflection 的内置支持——README 中明确说明,不带额外标志时 grpcurl 默认优先使用 reflection 获取 RPC schema(相关标志定义见 cmd/grpcurl/grpcurl.go)。
Bank 服务:一元 RPC 与流式 RPC 的完整示例
Bank 服务定义在 bank.proto 中,接口模型是"为当前已认证用户操作银行账户",鉴权身份通过 authorization 请求头传递(后文详述)。它同时包含一元 RPC和服务端流式 RPC两种形态,非常适合对照学习:
| OpenAccount | OpenAccountRequest | Account | 一元 | 以指定类型和初始存款开户 |
| CloseAccount | CloseAccountRequest | google.protobuf.Empty | 一元 | 关闭账户(余额必须为零) |
| GetAccounts | google.protobuf.Empty | GetAccountsResponse | 一元 | 列出当前客户的全部账户 |
| GetTransactions | GetTransactionsRequest | stream Transaction | 服务端流 | 按条件流式返回交易记录 |
| Deposit | DepositRequest | BalanceResponse | 一元 | 存款并返回新余额 |
| Withdraw | WithdrawRequest | BalanceResponse | 一元 | 取款并返回新余额 |
| Transfer | TransferRequest | TransferResponse | 一元 | 账户间转账,支持本行/外部 ACH 账户 |
几个值得关注的 schema 设计:
- 金额全部用"分"(cents)表示整数,如 initial_deposit_cents、amount_cents、balance_cents,避免浮点误差——服务端格式化函数 dollars()(db.go)把分值转成 $xx.xx 展示;
- Account 内嵌了 Type 枚举:UNKNOWN、CHECKING、SAVING、MONEY_MARKET、LINE_OF_CREDIT、LOAN、EQUITIES;
- DepositRequest 内嵌了入金来源枚举 Source:CASH、CHECK、ACH、WIRE;
- TransferRequest 通过两个 oneof 表达"源/目的账户",既可以填本行账号(source_account_number / dest_account_number),也可以填外部 ACH 账户(ach_routing_number + ach_account_number 构成的 ExternalAccount 嵌套消息);
- 时间类型使用标准 Well-Known Type google.protobuf.Timestamp(start、end、Transaction.date)。
服务端实现(bank.go)展示了 gRPC 服务端的典型错误处理模式,全部通过 status.Errorf 返回标准 gRPC 错误码:
- Unauthenticated:请求头缺少或无效的客户身份;
- InvalidArgument:开户初始存款非法(如 LINE_OF_CREDIT/LOAN/EQUITIES 必须为零初始存款、存款金额非正、取款金额非负、未知账户类型、未知入金来源、外部 ACH 账号/路由号为零);
- NotFound:客户名下不存在指定账号;
- FailedPrecondition:余额非零时关户、取款超余额(insufficient funds)。
用 grpcurl 调用 Bank 服务
首先用一个"客户身份"创建账户(-H 用于附加请求头,用法见 cmd/grpcurl/grpcurl.go):
grpcurl -plaintext -H 'authorization: token 1' \\
-d '{"initial_deposit_cents": 10000, "type": "CHECKING"}' \\
127.0.0.1:12345 Bank.OpenAccount
注意:grpcurl 的所有 -H、-d 等参数必须放在服务器地址与方法名之前。响应类似:
{
"accountNumber": "1",
"type": "CHECKING",
"balanceCents": 10000
}
再存一笔、查一下账户列表:
grpcurl -plaintext -H 'authorization: token 1' \\
-d '{"account_number": 1, "amount_cents": 5000, "source": "CASH", "desc": "coffee money"}' \\
127.0.0.1:12345 Bank.Deposit
grpcurl -plaintext -H 'authorization: token 1' \\
-d '{}' 127.0.0.1:12345 Bank.GetAccounts
GetAccounts 的请求类型是 google.protobuf.Empty,JSON 中传 {} 即可。若不带 authorization 头调用,服务端会返回 Unauthenticated 错误,这正是下一节要讲的鉴权模型。
基于 Header 的鉴权模型:如何区分客户与客服
bankdemo 的鉴权是一个刻意简化的教学示例,实现在 auth.go 中:它把 gRPC 元数据里的 authorization 请求头当作身份凭据,格式为:
authorization: token <credential>
解析规则(auth.go):
- 从 gRPC incoming context 中取 authorization 元数据,必须恰好一个值;
- 将值按空格拆成两段,前半段必须是小写的 token;
- 后半段即凭据内容,代码"把 token 直接当作 ID 使用"。
身份的"客户/客服"区分靠前缀约定(auth.go):
- getCustomer(ctx):凭据不以 agent 开头 → 视为客户 ID(如 token 1、token 42);
- getAgent(ctx):凭据必须以 agent 开头 → 视为客服 ID(如 token agent1、token agent2);
- 前缀不符的会被相应接口拒绝,例如用 agent 开头的 token 调银行接口,会得到 Unauthenticated。
因此用 grpcurl 模拟不同身份非常简单:
# 以"客户 1"身份调用银行接口
grpcurl -plaintext -H 'authorization: token 1' … Bank.OpenAccount
# 以"客服 agent1"身份接入客服聊天
grpcurl -plaintext -H 'authorization: token agent1' … Support.ChatAgent
从 grpcurl 侧看,-H 附加的请求头在反射查询和 RPC 调用中都会携带(-H 的说明见 cmd/grpcurl/grpcurl.go),所以带鉴权头调用时反射与真正的方法调用都能通过服务端校验。
服务端流实战:GetTransactions 的流式返回
Bank.GetTransactions 是典型的**服务端流(server-streaming)**方法:客户端发一个请求,服务端通过流连续返回多条 Transaction 消息。请求支持三个字段(bank.proto):
- account_number:必填,查询哪个账户;
- start:起始时间(google.protobuf.Timestamp),不填则从"时间起点"算起;
- end:结束时间,不填则包含到"当前/9999 年"为止的全部记录。
服务端逻辑(bank.go)会校验时间戳有效性(CheckValid),遍历账户的全部交易,把落在 [start, end] 区间内的记录逐条 stream.Send 出去。
调用示例(流式响应会逐条打印):
grpcurl -plaintext -H 'authorization: token 1' \\
-d '{"account_number": 1}' \\
127.0.0.1:12345 Bank.GetTransactions
如果账户 1 已有两条交易(开户初始存款 + 一笔入金),输出将是两条独立的 JSON 消息,各自带 date、amountCents、desc、seqNumber 字段。
全双工双向流:Support 聊天服务的状态机
Support 服务是 bankdemo 最精彩的部分,定义在 support.proto 中,用来演示全双工双向流:客户与客服各自维持一条双向流,消息可同时双向流动。它包含两个方法,两个都是 stream … returns (stream …):
- ChatCustomer(stream ChatCustomerRequest) returns (stream ChatCustomerResponse):仅客户(非 agent 前缀 token)可调用;客户负责发起/终止会话;
- ChatAgent(stream ChatAgentRequest) returns (stream ChatAgentResponse):仅客服(agent 前缀 token)可调用;客服可"接单"并回复消息。
proto 注释(support.proto)说明了核心抽象:一条流是一个有状态的"会话连接"。流初始处于"未连接"状态;必须先通过 init(客户)或 accept(客服)接入会话;要接入新会话,必须先通过 hang_up(客户)或 leave_session(客服)断开当前会话。
客户侧请求协议(oneof req)
| init | InitiateChat | 发起新会话;若带 resume_session_id 则尝试恢复指定会话 |
| msg | string | 向会话中的客服发送消息 |
| hang_up | Void | 终止会话;断流但未终止时,可另开新流用 init 恢复 |
客服侧请求协议(oneof req)
| accept | AcceptChat | 接入会话;session_id 为空则从**等待队列(FIFO)**取等待最久的会话,也可指定 ID 接入;多个客服可同时接入同一会话 |
| msg | string | 发送消息给客户及其他客服 |
| leave_session | Void | 退出当前会话(之后可随时重新 accept 该会话) |
服务端把会话建模为"客户 + 若干客服"的订阅关系,核心实现在 chat.go:
- newSession(chat.go):客户发起新会话,生成 %06d 格式的会话 ID,并加入 chatsAwaitingAgent 待接队列;
- resumeSession(chat.go):恢复会话有严格限制——会话必须仍 active、发起者必须是同一客户、且该会话当前没有其他客户流占用;
- acceptSession(chat.go):客服接单,默认从 FIFO 队列头部取单,多个客服可并存于同一 sess.agents map;
- closeSession(chat.go):客户挂断后,向所有客服推送 session_ended 通知并清理队列与会话表;
- 消息广播通过带缓冲的 channel + listener 实现(chat.go),send 在 channel 满或流已结束时优雅放弃,避免阻塞。
用 grpcurl 实操一次双向流聊天
双向流需要交互式输入:grpcurl 支持用 -d @ 从 stdin 读取请求体,交互式终端下可持续输入多条消息(-d 标志说明见 cmd/grpcurl/grpcurl.go)。终端 A 先以客户身份接入并开一个会话:
grpcurl -plaintext -H 'authorization: token 1' -d @ \\
127.0.0.1:12345 Support.ChatCustomer
随后在 stdin 中输入(每条一行 JSON):
{"init": {}}
{"msg": "hi, I need help with my account"}
init 的响应会返回 Session(含 sessionId、customerName、history)。注意保持该终端不退出(双向流持续在线)。
终端 B 以客服身份接单:
grpcurl -plaintext -H 'authorization: token agent1' -d @ \\
127.0.0.1:12345 Support.ChatAgent
stdin 中输入(先不填 session_id 走 FIFO 队列,也可填 "accept": {"session_id": "000001"} 指定会话):
{"accept": {}}
{"msg": "hello, how can I help?"}
此时终端 A 会实时收到客服消息(agentMsg),终端 B 会收到客户消息(ChatEntry)。客户想结束会话时发送 {"hang_up": "VOID"}(Void 是仅含 VOID=0 的枚举,JSON 中用字符串名表示),客服端会收到 session_ended 通知。若客户直接断开流而未 hang_up,客服仍在会话中时,客户可新开流发送 {"init": {"resume_session_id": "000001"}} 恢复原会话——这正是 proto 注释里描述的"断流不终止会话"语义。
由于双向流是长时间挂起的调用,实操时建议参考 README 对 grpcurl 流式能力的说明:流式方法完全被支持,交互式终端下用 stdin 作为请求体即可持续操作。
内存数据库与 JSON 持久化:accounts.json 的读写链路
bankdemo 的全部银行数据保存在一个"纯内存数据库"中,结构定义在 db.go:
| AccountNumbersByCustomer | map[string][]uint64 | 客户 → 其名下账号列表 |
| AccountsByNumber | map[uint64]*account | 账号 → 账户详情(含交易流水) |
| AccountNumbers | []uint64 | 全部账号(供遍历/关闭时清理) |
| Customers | []string | 全部客户 |
| LastAccountNum | uint64 | 自增账号号段生成器 |
| mu | sync.RWMutex | 读写锁,保护并发访问 |
并发安全是该实现的重点:openAccount、closeAccount 用 Lock() 独占写,getAccount、getAllAccounts 用 RLock() 共享读;单个账户的交易追加同样用账户级互斥锁保护(db.go),取款时还会校验余额不足并返回 FailedPrecondition。
持久化机制:定时 + 优雅关闭双保险
按 README 的说明,"每几秒、以及在收到 SIGTERM/SIGINT 等信号优雅关闭时"会把状态保存到 accounts.json。源码印证了这一设计:
- 定时保存:bgSaver() 协程用 time.NewTicker(5 * time.Second) 每 5 秒调用一次 flush()(main.go);
- 优雅关闭保存:main 中 defer s.flush() 保证进程退出前落盘(main.go),同时 signal.Notify 捕获 SIGINT/SIGTERM 后调用 grpcSvr.GracefulStop()(main.go);
- 启动恢复:load() 在服务启动前读取 accounts.json,文件不存在则初始化空数据库(main.go)。
flush() 的实现也体现了并发考量:先对内存数据做一次 clone() 快照再 json.Marshal 写盘(main.go),避免持锁写文件拖慢 RPC。此外,由于 Transaction 包含 google.protobuf.Timestamp,db.go 为它实现了基于 protojson 的自定义 MarshalJSON / UnmarshalJSON(db.go),保证交易记录能正确序列化进 JSON 文件并在重启后还原。
这也带来一个实用的测试技巧:重启 go run ./internal/testing/cmd/bankdemo 后,之前创建的所有账户、交易(甚至未挂断的聊天历史之外的银行数据)都会从 accounts.json 恢复,方便反复演示。
服务端拦截器与可复用的教学点
除了业务逻辑,bankdemo 在 main.go 中还注册了一元拦截器(UnaryInterceptor)和流拦截器(StreamInterceptor):每次请求都会用原子计数器生成 request N,记录 FullMethod 与对端地址,请求结束后打印 gRPC 状态码与消息。这让 grpcurl 的每次调用在服务端日志中都有迹可循,调试时非常直观。从源码结构看,这个模式完全可以作为自建 gRPC 服务的日志/监控骨架。
总结 bankdemo 值得迁移到真实项目的设计点:
结语
bankdemo 虽然只有少量源文件,却是理解 grpcurl 与 gRPC 服务端交互的最短路径:从 grpcurl -plaintext 127.0.0.1:12345 list 开始,到一元 RPC 的增删改查,再到服务端流式拉取交易、用两条终端双向流模拟一次真实的客户-客服聊天,整个链路全部可以在本仓库内完成验证。配合 README.md 中 grpcurl 的完整用法(-d、-H、list、describe、-protoset 等),读者可以把这个演示服务器当作 grpcurl 的"练习靶场",快速上手所有主流调用形态。
赞
【免费下载链接】grpcurl
Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers
项目地址:
https://gitcode.com/gh_mirrors/gr/grpcurl
点击查看 免费下载
相关推荐
如何用CogVideoX轻松生成高质量视频:从文本到动态影像的完整指南
CogVideoX多模态融合引擎:重塑视频生成技术新边界
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心




评论前必须登录!
注册