前言
在gRPC中,客户端应用程序可以直接在其他计算机上的服务器应用程序上调用方法,就好像它是本地对象一样,从而使您更轻松地创建分布式应用程序和服务。
与许多RPC系统一样,gRPC围绕定义服务的思想,指定可通过其参数和返回类型远程调用的方法。 在服务器端,服务器实现此接口并运行gRPC服务器以处理客户端调用。 在客户端,客户端具有一个存根(在某些语言中仅称为客户端),提供与服务器相同的方法。
例如,你可以使用Go,Python或Ruby的客户端轻松地用 Java 创建 gRPC 服务器。
前提知识点:
- protocol buffers
- RPC
更多详情,可以参考官网:https://www.grpc.io/docs/
安装
假设当前已经按照了protocol buffers
的编译器
gRPC
go get -u google.golang.org/grpc
安装包拉去成功一般会这样:
go: downloading google.golang.org/grpc v1.33.2
go: downloading github.com/google/go-cmp v0.5.0
go: downloading google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
go: downloading golang.org/x/net v0.0.0-20190311183353-d8887717615a
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
go: downloading golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
这里可能会下载失败,可以被长城了,可以换国内的 go mod
加速源:
https://mirrors.aliyun.com/goproxy/
## 即,set GOPROXY=https://mirrors.aliyun.com/goproxy/
protoc-gen-go
本文以 go 语言为示例,所以 proto 编译器还需要安装 protoc-gen-go
:
go get -u github.com/golang/protobuf/protoc-gen-go
gRPC实践
目录结构
λ tree /F
D:.
│ go.mod
│ go.sum
├─client
│ client.go
├─proto
│ helloworld.pb.go
│ helloworld.proto
└─server
server.go
IDL存根
定义服务
定义服务的方法,如下所示:
下面展示一个写好的.proto
文件:
编译
编译成.pb.go
文件
protoc --go_out=plugins=grpc:. helloworld.proto
-
--go_out
,输出 golang 代码 -
plugins=grpc
,proto默认是不会生成 RPC 代码的,需要指定插件,不指定则不会生成Service的接口 -
:
,分隔符 -
.
,表示文件输出路径,我指定输出到当前目录,当然你也可以指定目录 -
helloworld.proto
,表示需要转换的proto
文件路径,我指定的是当前目录下的helloworld.proto
文件
编译后
让我们查看生成的文件,其实,是一般RPC代码的一种自动化封装。
client
- 结构体
greeterClient
- 返回对象
NewGreeterClient
- 实现接口里的方法
SayHello
// greeterClient结构体
type greeterClient struct {...}
// 返回一个新的客户端对象
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {...}
// 实现接口
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {...}
server
- 结构体
UnimplementedGreeterServer
- 绑定的方法
SayHello
- 服务注册
RegisterGreeterServer
// server API 接口
type GreeterServer interface {...}
// UnimplementedGreeterServer结构体,可扩展
type UnimplementedGreeterServer struct {...}
// 绑定到“类”的方法
func (*UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {...}
// 注册服务
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {...}
服务端
参考官方示例:https://github.com/grpc/grpc-go/tree/master/examples
可知,实现 gRPC 服务,一般是通过一下几步实现:
- 设置监听,指定
IP
和port
package main
import (
"context"
pb "gRPC_example/proto" //引入生成的代码
"google.golang.org/grpc"
"log"
"net"
)
const (
port = ":50051"
)
// server 用于实现 helloworld.GreeterServer接口
type server struct{}
// SayHello 实现 helloworld.GreeterServer 接口里的方法
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
// 初始化一个gRPC的结构体对象
s := grpc.NewServer()
// 注册服务
pb.RegisterGreeterServer(s, &server{})
// 设置监听,指定IP 和 port
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 启动服务
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端
gRPC 客户端的一般操作流程是:
- 连接 gRPC 服务器
- 初始化gRPC 客户端
- 输入参数,通过服务名称调用gRPC服务
- 获得响应结果
package main
import (
"context"
"log"
"os"
"time"
pb "gRPC_example/proto"
"google.golang.org/grpc"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// 连接 gRPC 服务器
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
//初始化gRPC 客户端
c := pb.NewGreeterClient(conn)
// 输入参数
name := defaultName
if len(os.Args) > 1 { //如果外部没有接收到参数,则使用默认参数
name = os.Args[1]
}
// 设置超时
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 调用gRPC服务
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
结果验证
启动服务端:
go run server.go
启动客户端:
go run client.go [参数,可选]
结果如下:
总结
这篇博客中,介绍了gRPC的基本实现步骤,演示了如何使用golang的demo,实现一个简单的gRPC远程过程调用服务。总的来说,一般的步骤为:
- 根据需求,定义服务,写成
.proto
文件,作为IDL
存根; - 编译proto文件,成对应的语言;
- 根据第二步,写服务。