Golang 使用 protobuf 实现 grpc 通信示例。
介绍
安装 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
指定
源码安装
参考
直接到 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
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 客户端工具
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
其他