Golang 使用 protobuf 实现 grpc 通信

发布时间: 更新时间: 总字数:1241 阅读时间:3m 作者: IP属地: 分享 复制网址

Golang 使用 protobuf 实现 grpc 通信示例。

介绍

  • 使用 golang + protobuf 实现 rpc 通信

  • 当前使用原生 golang 开发,也可以参考 gogo/protobuf 实现

  • 概念

    • grpc 中给服务端叫 gRPC server,Server 端需要实现对应的 RPC,所有的 RPC 组成了 Service
    • 客户端叫 grpc Stub(不叫 grpc client)通过 Stub 可以真正的调用 RPC 请求
    • Channel 提供一个与特定 gRPC server 的主机和端口建立的连接
    • Stub 是在 Channel 的基础上封装

安装 protoc compiler

参考安装(旧版本参考protoc compiler 编译器

  • 安装包解压后放到 bin + include 目录(父目录为 /usr/local
    • unzip protoc-xx.x-linux-x86_64.zip -d /usr/local
  • include 的文件也可以通过 protoc -I/usr/local/include 指定

源码安装

参考

  • 二进制安装 protoc compiler

直接到 github 下载,注意选择合适的版本

export GO111MODULE=on
export GOPROXY=https://goproxy.io,direct
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

# old version
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

ubuntu 包安装

apt update
apt install protobuf-compiler golang-goprotobuf-dev

编写 proto 文件

使用 vscode 插件 vscode-proto3 开发 demo.proto

syntax = "proto3";
import "google/protobuf/empty.proto";

package main;
option go_package = "github.com/xiexianbin/go-rpc-demo/grpc";

message NumRequest {
    repeated int64 Nums = 1;
}

message NumResponse {
    int64 Result = 1;
}

message VersionResponse {
    string Version = 1;
}

message FilePath {
    string path = 1;
}

message FileResponse {
    bytes content = 1;
}

service Service {
    rpc Sum(NumRequest) returns (NumResponse) {}
    rpc Diff(NumRequest) returns (NumResponse) {}

    rpc Version(google.protobuf.Empty) returns (VersionResponse) {}
    rpc ReadFile(FilePath) returns (FileResponse) {}
}

编译生成结构体和方法

此次编译,生成 golang 对应的结构体和方法

  • 运行如下命令安装 Go protocol buffers plugin(protoc-gen-go 会被安装到 $GOBIN 目录下)
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
  • 生成结构体
SRC_DIR=.
DST_DIR=.
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/grpc/demo.proto
# protoc -I=$SRC_DIR --go_out=$DST_DIR --go_opt=paths=source_relative $SRC_DIR/grpc/demo.proto
# --plugin=$GOBIN/protoc-gen-go

在当前目录下生成 github.com/xiexianbin/go-rpc-demo/grpc/demo.pb.go 文件:

  • NumRequest、NumResponse、VersionResponse、FilePath、FileResponse 结构体和对应的方法

编译生成 gRPC 接口

  • 安装 protobuf 服务( Service )的 Golang 代码生成插件
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
  • 生成 grpc 文件
SRC_DIR=.
DST_DIR=.
protoc -I=$SRC_DIR --go-grpc_out=$DST_DIR $SRC_DIR/grpc/demo.proto
# protoc -I=$SRC_DIR --go-grpc_out=$DST_DIR --go-grpc_opt=paths=source_relative $SRC_DIR/grpc/demo.proto

在目录 github.com/xiexianbin/go-rpc-demo/grpc/demo_grpc.pb.go 文件,里面封装了 rpc 调用需要的方法

实现 gRPC Server 类

type DemoServiceServer struct {
	dgrpc.UnimplementedServiceServer
}

func (s *DemoServiceServer) Sum(ctx context.Context, numRequest *dgrpc.NumRequest) (*dgrpc.NumResponse, error) {
	...
	return numResponse, nil
}

func (s *DemoServiceServer) Diff(ctx context.Context, numRequest *dgrpc.NumRequest) (*dgrpc.NumResponse, error) {
	...
	return numResponse, nil
}

func (s *DemoServiceServer) Version(ctx context.Context, empty *emptypb.Empty) (*dgrpc.VersionResponse, error) {
	...
	return version, nil
}

func (s *DemoServiceServer) ReadFile(ctx context.Context, filePath *dgrpc.FilePath) (*dgrpc.FileResponse, error) {
	...
	return &dgrpc.FileResponse{Content: fileContent}, nil
}

服务监听

rpc 客户端工具

  • wombat rpc 客户端工具安装:
brew install --cask wombat

通过是否采用SSL证书、指定 proto 文件,可以调试 rpc 相关接口

  • grpcurl 像 cURL 一样,但用于 gRPC:用于与gRPC服务器交互的命令行工具
# 安装
brew install grpcurl

go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# 使用
grpcurl grpc.server.com:443 my.custom.server.Service/Method

# no TLS
grpcurl -plaintext grpc.server.com:80 my.custom.server.Service/Method

TSL 通信

证书签发

install and use xca to create tsl cert.

# 生成根证书
xca -create-ca true \
  -root-cert x-ca/ca/root-ca.crt \
  -root-key x-ca/ca/root-ca/private/root-ca.key \
  -tls-cert x-ca/ca/tls-ca.crt \
  -tls-key x-ca/ca/tls-ca/private/tls-ca.key

# 生成 server 证书
xca -cn server \
  --domains "localhost" \
  --ips 127.0.0.1 \
  -tls-cert x-ca/ca/tls-ca.crt \
  -tls-key x-ca/ca/tls-ca/private/tls-ca.key

# 生成 client 证书
xca -cn client \
  --domains "localhost" \
  --ips 127.0.0.1 \
  -tls-cert x-ca/ca/tls-ca.crt \
  -tls-key x-ca/ca/tls-ca/private/tls-ca.key

服务端加载证书

func loadServerTSLCert() (credentials.TransportCredentials, error) {
	caPEMFile, err := ioutil.ReadFile(rootCACrtPath)
	if err != nil {
		return nil, err
	}

	caPool := x509.NewCertPool()
	if !caPool.AppendCertsFromPEM(caPEMFile) {
		return nil, fmt.Errorf("load %s cert fail", rootCACrtPath)
	}

	localCert, err := tls.LoadX509KeyPair(serverCrtPath, serverKeyPath)
	if err != nil {
		return nil, fmt.Errorf("load server cert and key file fail: %s", err.Error())
	}

	config := &tls.Config{
		Certificates: []tls.Certificate{localCert},
		ClientAuth:   tls.RequireAndVerifyClientCert, // check client's certificate
		ClientCAs:    caPool,
	}

	return credentials.NewTLS(config), nil
}

	var s *googlerpc.Server
	if rootCACrtPath != "" && serverCrtPath != "" && serverKeyPath != "" {
		tlsCrt, err := loadServerTSLCert()
		if err != nil {
			log.Fatalf("load server cert err: %s", err.Error())
		}
		s = googlerpc.NewServer(googlerpc.Creds(tlsCrt))
	} else {
		s = googlerpc.NewServer()
	}

客户端加载证书

func loadClientTSLCert() (credentials.TransportCredentials, error) {
	caPEMFile, err := ioutil.ReadFile(rootCACrtPath2)
	if err != nil {
		return nil, err
	}

	caPool := x509.NewCertPool()
	if !caPool.AppendCertsFromPEM(caPEMFile) {
		return nil, fmt.Errorf("load %s cert fail", rootCACrtPath2)
	}

	localCert, err := tls.LoadX509KeyPair(clientCrtPath, clientKeyPath)
	if err != nil {
		return nil, fmt.Errorf("load client cert and key file fail: %s", err.Error())
	}

	config := &tls.Config{
		Certificates: []tls.Certificate{localCert},
		ServerName:   "localhost", // client cn name
		RootCAs:      caPool,
	}

	return credentials.NewTLS(config), nil
}

	var creds credentials.TransportCredentials
	var err error
	if rootCACrtPath2 != "" && clientCrtPath != "" && clientKeyPath != "" {
		creds, err = loadClientTSLCert()
		if err != nil {
			log.Fatalf("load client cert err: %s", err.Error())
		}
	} else {
		creds = insecure.NewCredentials()
	}
	cc, err := googlerpc.Dial("127.0.0.1:8000", googlerpc.WithTransportCredentials(creds))

启动服务

由于 xca 采用二级证书签发,因此服务端和客户端启动服务时,需要加载证书链,签发和使用详见源代码README.md

源代码

更多实现,如客户端、grpc+TSL实现,参考源代码:go-rpc-demo

其他

  • gRPC 压测工具:ghz
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数