深入理解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"
      

运行示例

获取示例代码

示例代码是grpc-go仓库的一部分。

  1. clone下仓库代码:

    $ git clone -b v1.50.0 --depth 1 https://github.com/grpc/grpc-go
    
  2. 切换到示例代码的文件夹:

    $ 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.gohelloworld/helloworld_grpc.pb.go文件。

github仓库中提供的示例代码里面已包含这两个文件,无需我们重新生成。

运行示例

  1. 编译并执行server端代码:

    $ go run greeter_server/main.go
    
  2. 打开另外一个终端,编译并执行client端代码:

    $ go run greeter_client/main.go
    Greeting: Hello world
    

如果输出内容和上述一致,那么恭喜你,你已经使用 gRPC 运行了一个客户端-服务器应用程序。

分析示例

我们回头再深入看看刚才那个示例中涉及到的代码,详细了解下整个过程。

示例中涉及到的所有代码及目录结构如下:

├── 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;
}
  1. syntax:表明此文件所使用的protocol语法版本为proto3。目前有两个主要版本:proto2、proto3。
  2. option:文件中的各个声明可以使用option进行注释。option不会改变声明的整体含义,但可能会影响它在特定上下文中的处理方式。你可以使用官方提供的option,也可以使用自定义的option。
  3. package:表示此文件所属的包名为helloworld。这里的package与Go中的package含义类似,可以避免message类型之间的名称冲突。
  4. message:消息定义,用于定义请求响应中涉及到的消息格式。此处定义了两种消息格式:HelloRequest、HelloReply。
  5. 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整体结构如下图所示:

  1. GreeterClient:根据pb中定义的Greeter service生成的client,它提供SayHello方法,用于调用远程RPC方法SayHello。

  2. GreeterServer:根据pb中定义的Greeter service生成的server接口,开发者需要实现对应的SayHello方法,填充自己的业务逻辑,处理client端的请求。

  3. 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")
    }
    
  4. UnsafeGreeterServer:接口,也是为了向前兼容,官方目前不推荐使用这个接口。

  5. NewGreeterClient:返回一个新的Greeter client。

  6. RegisterGreeterServer:用于向grpc注册Greeter server。

在这里插入图片描述

简单总结下:

  1. helloworld.pb.go:用于填充、序列化、获取message HelloRequestmessage HelloReply的代码。
  2. 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)
	}
}
  1. 创建了一个server的数据结构,实现了SayHello方法。SayHello方法中写的是我们具体的业务逻辑。
  2. 通过grpc.NewServer()生成了一个新的grpc server,并向其注册了greeter server。
  3. 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())
}
  1. 与grpc server建立连接,生成了一个conn对象。
  2. 根据conn对象新生成了一个greeter client对象。
  3. 调用greeter client的SayHello方法,获取rpc响应结果。

总结

本文以hello world为例,讲述了从pb文件生成golang代码,到grpc client端调用server端获取数据的整个过程。

后续我再详细讲解下其中的几个细节,包括:

  1. grpc.NewServer()做了什么;grpc server的数据结构;grpc server是如何路由的,即它是如何映射到具体的实现的。
  2. c.SayHello()做了什么,它是如何与server端进行通信的,包括如何建立连接、发送请求信息、获取响应结果。

推荐阅读

  • grpc-go快速开始:https://grpc.io/docs/languages/go/quickstart/
  • protocol buffer3语法指南:https://developers.google.com/protocol-buffers/docs/proto3#services
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值