示例:go

简要概述

对在 go 语言下使用 grpc 的快速入门。

环境准备

编译器安装

  1. Go 语言 环境安装;
  2. Protocol buffer 编译器 protoc 安装;
  3. 生成 go 语言 protocol buffer 插件:

Go Protobuf 插件

在 Go 的 Protobuf 生态中,protoc-gen-goprotoc-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:消息序列化生成器

  • 核心职责
  1. 生成Protobuf消息结构将.proto文件中定义的message转换为Go结构体
  2. 实现编解码接口生成Marshal/Unmarshal等序列化方法
  3. 字段访问器生成为可选字段生成GetXXX()方法
  4. 枚举映射转换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 服务生成器

  • 核心职责
  1. 生成服务接口:转换 .proto 中的 service 定义
  2. 创建客户端存根:生成可调用的客户端方法
  3. 生成服务端骨架:提供需实现的服务接口
  4. 注册方法生成:生成服务注册函数 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

开发场景选择

  1. 关注点分离:protoc-gen-go 专注数据表示,protoc-gen-go-grpc 专注通信逻辑;
  2. 版本兼容:需确保两者版本匹配(推荐使用最新稳定版);
  3. 工程实践:在微服务项目中通常需要同时使用两者;
  4. 性能影响:分离后生成的代码更专注,编译产物更精简。

正确使用这两个生成器是构建高效 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)