一、gRPC 基础
rpcgRPCProtobuf
gRPC
gRPCrpcProtobuf

二、开发前置准备
GolangGolanggRPC

主要需要 2 个工具的安装:

Protocol Bufferprotocprotocgolang

2.1 protoc

.protoGolang.protoxxx.go

共三种安装方法:通过包管理、预编译包安装和源码编译安装。
以下是详细说明。

2.1.1 包管理方式安装

# ubuntu
apt install -y protobuf-compiler

# mac
brew install protobuf

# 安装完毕记得检查版本号,保证版本 3 +
protoc --version  # Ensure compiler version is 3+

2.1.2 预编译二进制安装

protoc-21.6-linux-x86_64.zip


可以提取下载链接,直接下载到开发环境中,也可以下载并拷贝。

以直接下载为例:

# ---------
# 第一步下载
# wget 下载 zip 包
wget https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-x86_64.zip
# 或者 curl命令,二选一即可
curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-x86_64.zip

# ---------
# 第二步解压
# 下载完毕后解压
# 需要安装 unzip 并创建 ~/.local 文件夹
# apt install unzip zip; mkdir ~/.local
# 想放到其他目录也可自行配置
unzip protoc-21.6-linux-x86_64.zip -d ~/.local

# -----------------------------
# 第三步,将 protoc 加入环境变量,可[临时添加]或[永久添加],二选一即可
# 3.1 临时
export PATH="$PATH:~/.local/bin"

# 3.2 永久,根据使用的shell和使用习惯变更对应的shell配置文件
# /etc/profile, ~/.bashrc, ~/.zshrc xxx 等
# 此处以 /etc/profile 为例
vi /etc/profile
# 在末尾插入:
PATH="$PATH:~/.local/bin"
# 刷新环境变量
source /etc/profile

# ---------------
# 第四步,检验安装
# 查看 protoc version
protoc --version
# 出现:libprotoc 3.xx.x 即为安装成功

2.1.3 编译安装

2.2 Golang 插件

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

要想使用基于 Golang 的 gRPC,二者缺一不可。

两个工具的具体使用方法,见本文 4.1。

Tips:网上有些教程只安装了一个插件就可以使用了,因为使用的是旧版。新旧说明可以参考这里。


三、proto 文件语法

在这里,proto文件主要是为了定义一种 rpc 服务。

详细语法可以参考官方英文文档。

后文内容如出现语法不一致,以官方为准。

3.1 头部语法

syntax = "proto3"; // 版本声明

package hello; // 包名,proto 的包名,如果有多个 proto 文件,依此区分

option go_package="./;hello"; 
// option go_package = "[path];[name]";
// path: 输出存放地址
// name: 生成 go 文件所属 package name

// 还有这种写法:
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";
// 没有 ";[name]",会根据最后一个名称,也就是 'tutorialpb' 来作为 go 的 package name
// 个人不是很建议用这种方法

3.2 定义服务

语法如下:

service service_name{
    rpc function_name (msg_type1) returns (msg_type2);
}

上述语法说明:

service_namefunction_namemsg_type1msg_type2

3.3 定义消息类型

msg_type
// message 是关键字,msg_name 是自定义的消息名称
message msg_name {
    [访问修饰符]  [数据类型] [数据名称] = [num],
    optional string test1= 1;
    string test2= 2;
    string test3= 3;
}

语法说明:

requireoptionalrepeatedstringint32int64uint32uint64boolbytes
hello.proto
// hello.proto
syntax = "proto3";

package hello; // 包名

option go_package="./;hello";

// The Hello service definition.
service Hello {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message
message HelloRequest {
  string name = 1;
}

// The response message
message HelloReply {
  string message = 1;
}

四、Golang gRPC 开发

可参考 grpc 官方文档中 Go Basics tutorial。

4.1 将 proto 文件编译为 go 文件

借助第二部分下载的工具,以及第三部分的 proto 文件,可以生成 go 源码文件,具体操作如下:

建议创建一个单独的文件夹,来保存 proto 文件和生成的 go 文件(在 go 项目中)。

操作命令:

# Tips:将--plugin= 后边的 protoc-gen-go 和 protoc-gen-go-rpc 的前缀路径换成自己实际的地址
protoc -I=./ ./*.proto --plugin=/go/bin/protoc-gen-go --go_out=./ --plugin=/go/bin/protoc-gen-go-grpc --go-grpc_out=./

简单说明一下上述命令中的内容:

protoc-I=./ ./*.proto-I./*.proto--plugin=xxx --xxx_out=xxxprotoc-gen-go--go_outprotoc-gen-go-grpc--go-rpc_out
xxx.protoxxx.pb.goxxx_grpc.pb.go

4.2 gRPC server 端

官方文档给出的 server 端流程如下:

flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
  log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
...
grpcServer := grpc.NewServer(opts...)
pb.RegisterRouteGuideServer(grpcServer, newServer())
grpcServer.Serve(lis)

流程概况:

// 见 xxx_grpc.pb.go
// UnimplementedHelloServer must be embedded to have forward compatible implementations.
type UnimplementedHelloServer struct {
}
pb.RegisterxxxServer(...)sxxx_grpc.pb.goUnimplementedXXXServerprotoservicexxx_grpc.pb.go
// 节选自 hello_grpc.pb.go 部分代码,展示应该实现的具体方法

// HelloServer is the server API for Hello service.
// All implementations must embed UnimplementedHelloServer
// for forward compatibility
type HelloServer interface {
	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
	mustEmbedUnimplementedHelloServer()
}
proto
// myserver.go
package main

import (
	"net"

	pb "grpc_demo/proto"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// 该结构体需要包含 hello_grpc.pb.go 中的 Unimplementedxxxx
type helloService struct {
	pb.UnimplementedHelloServer
}

// 定义具体的方法,与业务相关的核心内容
// 限定传输参数见 hello_grpc.pb.go 中定义的 interface
func (s helloService) SayHello(c context.Context, r *pb.HelloRequest) (*pb.HelloReply, error) {
	ret := new(pb.HelloReply)
	ret.Message = "hello, " + r.Name

	return ret, nil
}

// 展示了一个 grpc server 的创建监听、绑定grpc服务的全过程
func myServer() {
	lis, err := net.Listen("tcp", ":3399")
	if err != nil {
		panic(err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterHelloServer(grpcServer, &helloService{})
	grpcServer.Serve(lis)
}

func main() {
	myServer()
}

4.3 gRPC client 端

client 端首先需要建立一个 grpc 连接,流程如下:

var opts []grpc.DialOption
...
conn, err := grpc.Dial(*serverAddr, opts...)
if err != nil {
  ...
}
defer conn.Close()

其中:

serverAddroptsinsecure.NewCredentials()
connxxx_grpc.pb.goNewxxxClientclient
clientrequestxxx.pb.goproto
clientxxx_grpc.pb.go

同样,以第三部分和 4.2 server 端代码为准,构造 client 端代码:

package main

import (
	"fmt"
	pb "grpc_learn/proto"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func myClient() {
	conn, err := grpc.Dial(":3399", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		// handle error
		panic(err)
	}
	defer conn.Close()

	client := pb.NewHelloClient(conn)

	req := pb.HelloRequest{
		Name: "world",
	}
	reply, err := client.SayHello(context.Background(), &req)
	if err != nil {
		fmt.Println("client.SayHello error:", err)
		return
	}

	fmt.Printf("get msg from server:[%v] \n", reply)

}

func main() {
	myClient()
}


五、demo
proto

前文已经给出了各代码,这里做个总结汇总。

5.1 各文件及编译

文件目录:

./
├── myclient.go
├── myserver.go
└── proto
    ├── hello.pb.go
    ├── hello.proto
    └── hello_grpc.pb.go

hello.proto 文件

// proto/hello.proto

syntax = "proto3";

package hello; // 包名

option go_package="./;hello";

// The Hello service definition.
service Hello {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message
message HelloRequest {
  string name = 1;
}

// The response message
message HelloReply {
  string message = 1;
}

proto
protoc -I=./ ./*.proto --plugin=/go/bin/protoc-gen-go --go_out=./ --plugin=/go/bin/protoc-gen-go-grpc --go-grpc_out=./

服务端:

// myserver.go
package main

import (
	"net"

	pb "grpc_demo/proto"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

type helloService struct {
	pb.UnimplementedHelloServer
}

func (s helloService) SayHello(c context.Context, r *pb.HelloRequest) (*pb.HelloReply, error) {
	ret := new(pb.HelloReply)
	ret.Message = "hello, " + r.Name

	return ret, nil
}

func myServer() {
	lis, err := net.Listen("tcp", ":3399")
	if err != nil {
		panic(err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterHelloServer(grpcServer, &helloService{})
	grpcServer.Serve(lis)
}

func main() {
	myServer()
}

客户端:

// client.go
package main

import (
	"fmt"
	pb "grpc_learn/proto"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func myClient() {
	conn, err := grpc.Dial(":3399", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		// handle error
		panic(err)
	}
	defer conn.Close()

	client := pb.NewHelloClient(conn)

	req := pb.HelloRequest{
		Name: "world",
	}
	reply, err := client.SayHello(context.Background(), &req)
	if err != nil {
		fmt.Println("client.SayHello error:", err)
		return
	}

	fmt.Printf("get msg from server:[%v] \n", reply)

}

func main() {
	myClient()
}

5.2 结果

go run myserver.gogo run myclient.go
// myclient.go 响应
get msg from server:[message:"hello, world"]

写在最后:
由于时间限制和本人水平不足,本文可能存在错误和不足,欢迎指正。