go-grpc 示例

阅读:24
作者:majingjing
发布:2026-03-14 19:20:20

Go gRPC 学习笔记

一、环境准备

1. 安装 Go(>=1.20)

确保已安装 Go,并设置好 GOPATH(现代 Go 一般用 module,无需手动设 GOPATH)。

go version

2. 安装 Protocol Buffer 编译器(protoc)

macOS(用 Homebrew):

brew install protobuf

Linux(Ubuntu/Debian):

sudo apt install -y protobuf-compiler

Windows:

https://github.com/protocolbuffers/protobuf/releases 下载并解压,加入 PATH。

验证:

protoc --version
# 应输出:libprotoc x.x.x

3. 安装 Go 的 protoc 插件(用于生成 Go 代码)

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

注意:这两个命令会把可执行文件安装到 $GOPATH/bin(通常是 ~/go/bin),请确保该路径在你的 PATH 环境变量中。

验证:

protoc-gen-go --version
protoc-gen-go-grpc --version

二、定义 .proto 文件

文件路径:proto/helloworld.proto

// 指定使用 proto3 语法(最新版)
syntax = "proto3";

// 指定生成的 Go 包路径(相对于项目根目录)
// 注意:必须与实际 Go import 路径一致
option go_package = "./proto";

// 定义包名(避免命名冲突)
package helloworld;

// 定义一个 gRPC 服务 Greeter
service Greeter {
  // 定义一个 RPC 方法 SayHello
  // 输入是 HelloRequest,输出是 HelloReply
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// 定义请求消息结构
message HelloRequest {
  string name = 1;  // 字段编号 1,用于序列化
}

// 定义响应消息结构
message HelloReply {
  string message = 1;  // 字段编号 1
}

📌 执行命令生成代码:

protoc --go_out=. --go-grpc_out=. proto/helloworld.proto

三、gRPC Server

文件路径:server/main.go

package main

import (
	"context"       // 上下文,用于超时/取消
	"log"           // 日志
	"net"           // 网络监听
	"grpc-demo/proto" // 导入生成的 proto 代码(根据你的 module 名调整)
	"google.golang.org/grpc"
)

// 定义 server 结构体,嵌入 UnimplementedGreeterServer
// 这样即使只实现部分方法也不会报错(兼容未来新增方法)
type server struct {
	proto.UnimplementedGreeterServer
}

// 实现 SayHello 方法(必须与 .proto 中定义的方法签名一致)
func (s *server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
	// 构造响应:拼接字符串
	return &proto.HelloReply{Message: "Hello, " + req.GetName()}, nil
}

func main() {
	// 监听 TCP 端口 50051(gRPC 默认端口)
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("监听失败: %v", err)
	}

	// 创建 gRPC 服务器实例
	grpcServer := grpc.NewServer()

	// 注册 Greeter 服务到服务器(传入我们实现的 server 实例)
	proto.RegisterGreeterServer(grpcServer, &server{})

	log.Println("gRPC 服务器启动,监听 :50051")
	// 启动服务,阻塞等待连接
	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("服务器启动失败: %v", err)
	}
}

四、gRPC Client

文件路径:client/main.go

package main

import (
	"context"
	"log"
	"time"

	"grpc-demo/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure" // 用于不加密连接
)

func main() {
	// 创建与 gRPC 服务器的连接
	// 使用 insecure 凭据(无 TLS),仅用于测试
	conn, err := grpc.Dial(
		"localhost:50051",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		log.Fatalf("无法连接服务器: %v", err)
	}
	// 程序退出前关闭连接
	defer conn.Close()

	// 创建 Greeter 客户端(由 protoc 自动生成)
	client := proto.NewGreeterClient(conn)

	// 创建带 1 秒超时的上下文(防止卡死)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// 调用远程 SayHello 方法(像调本地函数一样)
	resp, err := client.SayHello(ctx, &proto.HelloRequest{Name: "Go"})
	if err != nil {
		log.Fatalf("调用 SayHello 失败: %v", err)
	}

	// 打印服务器返回的消息
	log.Printf("收到回复: %s", resp.GetMessage())
}

五、高级功能 1:拦截器(Interceptor)

什么是拦截器?

  • 类似中间件,可在 RPC 调用前后执行逻辑(如日志、认证、监控等)
  • 分为 Unary(一元)Stream(流式) 拦截器

示例:添加 Server 端日志拦截器

修改 server/main.gomain() 函数:

// 新增:定义一个简单的 Unary 拦截器
func loggingInterceptor(
	ctx context.Context,
	req interface{},
	info *grpc.UnaryServerInfo,
	handler grpc.UnaryHandler,
) (interface{}, error) {
	// 调用前记录
	log.Printf("收到 RPC 请求: %s", info.FullMethod)
	
	// 继续处理请求
	resp, err := handler(ctx, req)
	
	// 调用后记录(可选)
	if err != nil {
		log.Printf("RPC 返回错误: %v", err)
	} else {
		log.Printf("RPC 成功完成: %s", info.FullMethod)
	}
	return resp, err
}

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("监听失败: %v", err)
	}

	// 创建带拦截器的 gRPC 服务器
	grpcServer := grpc.NewServer(
		grpc.UnaryInterceptor(loggingInterceptor), // 注册拦截器
	)

	proto.RegisterGreeterServer(grpcServer, &server{})
	log.Println("带拦截器的 gRPC 服务器启动")
	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("服务器启动失败: %v", err)
	}
}

✅ 现在每次调用 SayHello 都会打印日志!


六、高级功能 2:启用 TLS 加密通信

步骤 1:生成自签名证书(测试用)

# 生成私钥
openssl genrsa -out server.key 2048

# 生成证书(Common Name 填 localhost)
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650

会提示输入信息,Common Name 必须为 localhost(或你实际访问的域名)

步骤 2:修改 Server(启用 TLS)

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	// ... 其他导入
)

func main() {
	// 读取证书和私钥
	cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
	if err != nil {
		log.Fatalf("加载证书失败: %v", err)
	}

	// 创建 TLS 配置
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.NoClientCert, // 不验证客户端证书(单向 TLS)
	}

	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatal(err)
	}

	// 使用 TLS 凭据创建 gRPC 服务器
	grpcServer := grpc.NewServer(
		grpc.Creds(credentials.NewTLS(tlsConfig)),
	)

	proto.RegisterGreeterServer(grpcServer, &server{})
	log.Println("gRPC 服务器(TLS)启动")
	grpcServer.Serve(lis)
}

步骤 3:修改 Client(信任服务器证书)

// 读取服务器证书(用于验证)
creds, err := credentials.NewClientTLSFromFile("server.crt", "localhost")
if err != nil {
	log.Fatalf("加载客户端 TLS 凭据失败: %v", err)
}

// 使用 TLS 连接
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))
// ... 后续不变

🔒 现在通信已加密!生产环境应使用正式 CA 签发的证书。


七、总结

功能说明
.proto定义服务和消息
protoc生成 Go 代码
Server实现接口 + 启动服务
Client创建连接 + 调用方法
拦截器添加通用逻辑(日志/认证等)
TLS加密通信,提升安全性

💡 初学建议:先掌握基本 Server/Client,再逐步加入拦截器和 TLS。