对应视频教程在哔哩哔哩
https://space.bilibili.com/523248241
一、先看故事:餐厅点餐
假设你要开一家连锁餐厅(分布式系统),总店(服务提供方)和分店(服务调用方)不在一个地方。
-
RPC(远程调用思想) :分店想用总店的"计算优惠价"功能,就像打电话问总店------拨号、问问题、等回答,跟问身边员工一样简单。
-
gRPC(框架) :你选了顺丰同城专送作为通信方式,速度快、能实时回传进度(流式)。
-
Protobuf(数据格式) :你把菜单和价格写在标准模板纸 上(.proto文件),总店和分店都用这个模板,省去了口头复述的麻烦和错误(二进制高效传输)。
-
注册中心(Nacos) :总店开张时,去**外卖平台(注册中心)**登记地址和电话。分店要下单时,先去外卖平台查"最近的总店在哪"(服务发现)。
-
熔断(Sentinel) :如果某家总店今天厨师生病出餐极慢,分店直接不再给这家店下单(熔断),防止整个午餐配送瘫痪。
二、 需要安装的内容
1.Protocol Buffers 编译器 (protoc)
https://github.com/protocolbuffers/protobuf/releases
Windows: 从官网下载protoc.exe放到PATH目录
验证:protoc --version
C:\Users\Administrator>protoc --version
libprotoc 35.1
2.Go 插件(生成 gRPC 代码用)
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
验证:获取GOPATH路径 你刚刚的安装就是在GOPATH\bin目录下,去查看你的安装exe文件是否存在,同时把GOPATH\bin的目录放入系统环境变量中
go env GOPATH
验证版本命令:
protoc-gen-go-grpc --version
显示内容:
protoc-gen-go-grpc 1.6.2
3.三个插件的关系
三者各自定位
1. protoc(核心编译器,本体)
Protocol Buffers 官方命令行编译器,独立二进制程序。
作用:解析 .proto 协议文件,完成语法校验、依赖解析、类型检查,本身不生成任何语言代码,只负责调度插件生成代码。
语法模板:
bash
运行
protoc --插件名=输出目录 源文件.proto
2. protoc-gen-go(Go 基础代码生成插件)
protoc 的Go 代码生成插件,专门生成 protobuf 序列化结构体代码(.pb.go)。
职责:
根据 proto message/enum/service 生成 Go 结构体
实现 proto.Message 接口、序列化 / 反序列化、JSON 转换、字段 getter/setter
只处理普通 proto 数据结构,不生成 gRPC 服务代码
3. protoc-gen-go-grpc(gRPC Go 代码生成插件)
protoc 的gRPC 专用插件,配套 gRPC Go 使用,生成 gRPC 服务端 / 客户端存根代码(_grpc.pb.go)。
职责:
识别 proto 里的 service + rpc 定义
生成服务端接口、客户端调用方法、流式请求 / 响应封装
依赖 protoc-gen-go 生成的基础 .pb.go,不能单独使用
二、层级依赖关系
plaintext
.proto 文件
↓
protoc(调度器)
├─→ protoc-gen-go → xxx.pb.go 数据结构体
└─→ protoc-gen-go-grpc → xxx_grpc.pb.go gRPC 服务/客户端
protoc 是总入口,必须先装;
写普通 proto(仅数据结构):只需要 protoc + protoc-gen-go;
写 gRPC 微服务(含 service/rpc):三者缺一不可;
protoc-gen-go-grpc 强依赖 protoc-gen-go 产出的代码,顺序无强制,但必须同时生成。
三、简单示例(golang代码实现)
3.1.项目依赖
go mod init mygrpc # 初始化模块
go get google.golang.org/grpc
go get google.golang.org/protobuf
3.2项目结构

3.3代码内容
1. calculator.proto
syntax = "proto3";
package calculator;
//option go_package = "输出目录;Go代码包名";
option go_package = "./;calculator";
service Calculator {
rpc Add (AddRequest) returns (AddResponse);
}
message AddRequest {
int32 a = 1;
int32 b = 2;
}
message AddResponse {
int32 result = 1;
}
2. 生成代码(在项目根目录执行)
protoc --go_out=. --go-grpc_out=. calculator.proto
执行后会自动生成 calculator.pb.go 和 calculator_grpc.pb.go
运行的命令的注释:
1. 基础主体 protoc
Protocol Buffers 官方编译器,用来把 .proto 协议文件编译生成各语言代码。
2. 参数拆解
① --go_out=.
--go_out:生成 protobuf 基础 Go 代码 的插件参数(结构体、序列化、反序列化、消息定义)
.:输出目录为当前文件夹
作用:根据 proto 里的 message 消息,生成 xxx.pb.go 文件。
② --go-grpc_out=.
--go-grpc_out:生成 gRPC Go 服务代码 的插件参数(服务接口、客户端、服务端存根)
.:输出到当前目录
作用:根据 proto 里的 service 服务定义,生成 xxx_grpc.pb.go 文件。
③ calculator.proto
输入源文件:要编译的 protobuf 协议文件,里面定义了计算器的消息和 gRPC 服务。
3. 执行后会生成两个文件
calculator.pb.go:纯 protobuf 消息序列化代码
calculator_grpc.pb.go:gRPC Client、Server 接口代码
备注:需要关注"calculator.proto"文件中的 "option go_package = "输出目录;Go代码包名";"的选项,特别是他的输出目录决定最终生成文件的目录下。
3. server.go
package main
import (
"context"
"log"
"net"
pb "mygrpc" // 导入生成的代码
"google.golang.org/grpc"
)
// 服务端实现
type server struct {
pb.UnimplementedCalculatorServer
}
// 实现 Add 方法
func (s *server) Add(ctx context.Context, req *pb.AddRequest) (*pb.AddResponse, error) {
log.Printf("收到请求: %d + %d", req.A, req.B)
return &pb.AddResponse{Result: req.A + req.B}, nil
}
func main() {
// 监听端口
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalf("监听失败: %v", err)
}
// 创建 gRPC 服务器
s := grpc.NewServer()
pb.RegisterCalculatorServer(s, &server{})
log.Println("✅ 服务端启动成功,监听端口 :8080")
if err := s.Serve(lis); err != nil {
log.Fatalf("启动失败: %v", err)
}
}
4.client代码
package main
import (
"context"
"log"
"time"
pb "mygrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// 连接服务端
conn, err := grpc.NewClient("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer conn.Close()
// 创建客户端
client := pb.NewCalculatorClient(conn)
// 调用 Add 方法
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.Add(ctx, &pb.AddRequest{A: 10, B: 20})
if err != nil {
log.Fatalf("调用失败: %v", err)
}
log.Printf("✅ 计算结果: 10 + 20 = %d", resp.Result)
}
5.代码运行
1.首先在根目录下 go mod tidy
2.go run server/server.go
3.go run client/client.go
4.客户端显示内容:
- ✅ 计算结果: 10 + 20 = 30
就是完成内容;