示例:go
3 分钟阅读
简要概述
对在 go 语言下使用 grpc 的快速入门。
环境准备
编译器安装
- Go 语言 环境安装;
- Protocol buffer 编译器 protoc 安装;
- 生成 go 语言 protocol buffer 插件:
- protoc-gen-go 消息序列化生成器安装;
- protoc-gen-go-grpc gRPC 服务生成器安装;
Go Protobuf 插件
在 Go 的 Protobuf 生态中,protoc-gen-go
与 protoc-gen-go-grpc
也经历了一段历史演进:
时期 | 代码生成方式 | 特点 |
---|---|---|
v1.20 前 | 单一protoc-gen-go |
同时生成消息和 gRPC 代码 |
v1.20+ | 拆分为两个独立插件 | 分离核心消息与 gRPC 服务生成逻辑 |
在新版本后依赖关系图解:
graph LR
Proto[.proto文件] -->|protoc-gen-go| Messages[消息结构.pb.go]
Proto -->|protoc-gen-go-grpc| Service[gRPC服务_grpc.pb.go]
Service -->|依赖| Messages
Service -->|依赖| grpc[google.golang.org/grpc]
它们的职责和生成内容分工:
1. protoc-gen-go
:消息序列化生成器
- 核心职责
- 生成Protobuf消息结构将
.proto
文件中定义的message
转换为Go结构体 - 实现编解码接口生成
Marshal/Unmarshal
等序列化方法 - 字段访问器生成为可选字段生成
GetXXX()
方法 - 枚举映射转换Protobuf枚举类型为Go的
const
枚举
- 生成文件示例 “helloworld.pb.go”
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"`
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_helloworld_helloworld_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
2. protoc-gen-go-grpc
:gRPC 服务生成器
- 核心职责
- 生成服务接口:转换
.proto
中的service
定义 - 创建客户端存根:生成可调用的客户端方法
- 生成服务端骨架:提供需实现的服务接口
- 注册方法生成:生成服务注册函数
RegisterXXXServer
- 生成文件示例 “helloworld_grpc.pb.go”
// 客户端存根
// HelloServiceClient is the client API for HelloService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type HelloServiceClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
}
type helloServiceClient struct {
cc grpc.ClientConnInterface
}
func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient {
return &helloServiceClient{cc}
}
......
// 服务端接口
// HelloServiceServer is the server API for HelloService service.
// All implementations must embed UnimplementedHelloServiceServer
// for forward compatibility
type HelloServiceServer interface {
SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
mustEmbedUnimplementedHelloServiceServer()
}
// UnimplementedHelloServiceServer must be embedded to have forward compatible implementations.
type UnimplementedHelloServiceServer struct {
}
func (UnimplementedHelloServiceServer) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
// 注册方法
func RegisterHelloServiceServer(s grpc.ServiceRegistrar, srv HelloServiceServer) {
s.RegisterService(&HelloService_ServiceDesc, srv)
}
3. 典型编译命令
# 同时安装两个生成器
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
开始 gRPC 服务
通过 proto 语言定义
编写 “helloworld/helloworld.proto” 文件内容:
$ mkdir helloworld
$ vim helloworld/helloworld.proto
syntax = "proto3";
package default;
option go_package = "default/helloworld";
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
通过 protoc 工具编译
- 仅生成消息体
protoc \
--go_out=. \
--go_opt=paths=source_relative \
helloworld/helloworld.proto
编译生成 “helloworld/helloworld.pb.go” 文件。
- 仅生成服务体
protoc \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
编译生成 “helloworld/helloworld_grpc.pb.go” 文件。
- 或一次性生成两种代码
protoc \
--go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
开发场景选择
- 关注点分离:
protoc-gen-go
专注数据表示,protoc-gen-go-grpc
专注通信逻辑; - 版本兼容:需确保两者版本匹配(推荐使用最新稳定版);
- 工程实践:在微服务项目中通常需要同时使用两者;
- 性能影响:分离后生成的代码更专注,编译产物更精简。
正确使用这两个生成器是构建高效 gRPC 服务的基石,开发者应根据实际需求选择生成策略。
实现 grpc 服务端
$ vim main.go
package main
import (
"context"
"fmt"
"log"
"net"
"google.golang.org/grpc"
"test/helloworld"
)
type testHello struct {
helloworld.UnimplementedHelloServiceServer
Name string
}
// SayHello
func (t *testHello) SayHello(ctx context.Context, req *helloworld.HelloRequest) (*helloworld.HelloResponse, error) {
result := &helloworld.HelloResponse{Reply: "hello world!"}
// TODO; ...
return result, nil
}
func main() {
fmt.Println("start test grpc server")
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", 10081))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
helloworld.RegisterHelloServiceServer(grpcServer, &testHello{Name: "test"})
grpcServer.Serve(lis)
}
$ go mod init test
$ go mod tidy
$ go run main.go
客户端接入
Go 客户端
依赖 go 语言存根,生成方式见通过 protoc 工具编译。
package main
import (
"context"
"fmt"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"test/helloworld"
)
func main() {
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err := grpc.NewClient("127.0.0.1:10081", opts...)
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
defer conn.Close()
client := helloworld.NewHelloServiceClient(conn)
req := &helloworld.HelloRequest{}
ctx := context.TODO()
resp, err := client.SayHello(ctx, req)
if err != nil {
log.Fatalf("failed to rpc server: %v", err)
}
fmt.Println("resp:", resp)
}
$ go run client.go
resp: reply:"hello world!"
$
Python 客户端
依赖 python 语言存根,生成方式如:
- 安装依赖
$ python3 -m pip3 install grpcio
$ python3 -m pip3 install grpcio-tools
- 生成 python 语言存根
python3 \
-m grpc_tools.protoc \
-I ./ \
--python_out=. \
--grpc_python_out=. \
helloworld/helloworld.proto
- 实现客户端调用通讯
import grpc
from helloworld import helloworld_pb2
from helloworld import helloworld_pb2_grpc
grpc_endpoint = "127.0.0.1:10081"
def main():
channel = grpc.insecure_channel(grpc_endpoint)
stub = helloworld_pb2_grpc.HelloServiceStub(channel)
req = helloworld_pb2.HelloRequest()
resp = stub.SayHello(req)
print(resp)
if __name__ == "__main__":
main()
$ python3 client.py
reply: "hello world!"
$
最后修改 May 19, 2025: chore: 添加 python 输出示例 (b11adc9)