深入理解grpc-go(一):从hello world说起
之前已分两篇文章讲解了grpc及其原理,本篇继续grpc的话题,讲讲grpc的golang实现grpc-go。
我们以grpc-go提供的hello world为例,分多篇讲讲grpc-go中client端和server端的整个通信过程的实现方式。
准备工作
Go,安装Go最新的三个版本中的任意一个都可。有关安装的方法,可参阅Go的Getting Started指南。
Protocol buffer编译器,protoc 3。有关安装的方法,可参阅Protocol Buffer Compiler Installation。
protoc的Go插件:
- 使用以下命令安装protoc的Go插件:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
- 更新你的环境变量
PATH
,以便protoc
能找到这个插件:$ export PATH="$PATH:$(go env GOPATH)/bin"
- 使用以下命令安装protoc的Go插件:
运行示例
获取示例代码
示例代码是grpc-go仓库的一部分。
clone下仓库代码:
$ git clone -b v1.50.0 --depth 1 https://github.com/grpc/grpc-go
切换到示例代码的文件夹:
$ cd grpc-go/examples/helloworld
生成gRPC代码
运行以下命令:
$ protoc --go_out=. --go_opt=paths=source_relative
--go-grpc_out=. --go-grpc_opt=paths=source_relative
helloworld/helloworld.proto
这将根据helloworld.proto中定义的数据结构生成helloworld/helloworld.pb.go
和helloworld/helloworld_grpc.pb.go
文件。
github仓库中提供的示例代码里面已包含这两个文件,无需我们重新生成。
运行示例
- 编译并执行server端代码:
$ go run greeter_server/main.go
- 打开另外一个终端,编译并执行client端代码:
如果输出内容和上述一致,那么恭喜你,你已经使用 gRPC 运行了一个客户端-服务器应用程序。$ go run greeter_client/main.go Greeting: Hello world
分析示例
我们回头再深入看看刚才那个示例中涉及到的代码,详细了解下整个过程。
示例中涉及到的所有代码及目录结构如下:
├── greeter_client
│ └── main.go
├── greeter_server
│ └── main.go
└── helloworld
├── helloworld.pb.go
├── helloworld.proto
└── helloworld_grpc.pb.go
helloworld.proto
Helloworld.proto是使用protocol buffer接口描述语言所定义的数据结构,具体内容如下:
syntax = "proto3";
option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
syntax:表明此文件所使用的protocol语法版本为proto3。目前有两个主要版本:proto2、proto3。
option:文件中的各个声明可以使用option进行注释。option不会改变声明的整体含义,但可能会影响它在特定上下文中的处理方式。你可以使用官方提供的option,也可以使用自定义的option。
package:表示此文件所属的包名为helloworld。这里的package与Go中的package含义类似,可以避免message类型之间的名称冲突。
message:消息定义,用于定义请求响应中涉及到的消息格式。此处定义了两种消息格式:HelloRequest、HelloReply。
service:服务定义,用来定义对外提供的服务接口格式。此处定义了一种服务Greeter,它有一个rpc方法SayHello。
helloword.pb.go & helloword_grpc.pb.go
这两个文件是protoc工具根据helloworld.proto文件生成的对应的golang代码。
helloworld.pb.go整体结构如下图所示。其中File_examples_helloworld_helloworld_helloworld_proto存储的是pb中的一些元数据,包括pb中定义了多少个enum、message、service等;HelloRequest、HelloReply是根据pb中定义的message HelloRequest、message HelloReply转换得到的对应的go struct。
helloworld_grpc.pb.go整体结构如下图所示:
GreeterClient:根据pb中定义的Greeter service生成的client,它提供SayHello方法,用于调用远程RPC方法SayHello。
GreeterServer:根据pb中定义的Greeter service生成的server接口,开发者需要实现对应的SayHello方法,填充自己的业务逻辑,处理client端的请求。
UnimplementedGreeterServer:GreeterServer的一个内置实现,主要是为了向前兼容。它的Sayhello方法实现如下,直接返回“method SayHello not implemented”的error。
func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") }
UnsafeGreeterServer:接口,也是为了向前兼容,官方目前不推荐使用这个接口。
NewGreeterClient:返回一个新的Greeter client。
RegisterGreeterServer:用于向grpc注册Greeter server。
简单总结下:
helloworld.pb.go:用于填充、序列化、获取
message HelloRequest
和message HelloReply
的代码。helloworld_grpc.pb.go:生成的client端和server端代码。
greeter_server/main.go
greeter_server/main.go的主要代码如下:
var (
port = flag.Int("port", 50051, "The server port")
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements 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() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
创建了一个server的数据结构,实现了SayHello方法。SayHello方法中写的是我们具体的业务逻辑。
通过grpc.NewServer()生成了一个新的grpc server,并向其注册了greeter server。
grpc server监听port端口,并处理从此端口中接收到的rpc请求。
greeter_client/main.go
greeter_client/main.go的主要代码如下:
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
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())
}
与grpc server建立连接,生成了一个conn对象。
根据conn对象新生成了一个greeter client对象。
调用greeter client的SayHello方法,获取rpc响应结果。
总结
本文以hello world为例,讲述了从pb文件生成golang代码,到grpc client端调用server端获取数据的整个过程。
后续我再详细讲解下其中的几个细节,包括:
grpc.NewServer()做了什么;grpc server的数据结构;grpc server是如何路由的,即它是如何映射到具体的实现的。
c.SayHello()做了什么,它是如何与server端进行通信的,包括如何建立连接、发送请求信息、获取响应结果。
推荐阅读
grpc-go快速开始:grpc.io/docs/languages/go/quicksta...
protocol buffer3语法指南:developers.google.com/protocol-buf...
关于我
大厂搬砖人员,有多年的微服务开发经验,精通 Go、Python 语言。关注我了解更多⬇️
本作品采用《CC 协议》,转载必须注明作者和本文链接