# grpc+cobra-demo **Repository Path**: coderxkk/grpc-cobra-demo ## Basic Information - **Project Name**: grpc+cobra-demo - **Description**: No description available - **Primary Language**: Go - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-02-10 - **Last Updated**: 2022-02-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # grpc+cobra demo ### 介绍 cobra是go语言的一个库,可以用于编写命令行工具。 gRPC(Google Remote Procedure Calls)是一个高性能、开源、通用的RPC框架,面向移动和HTTP/2设计,是由google发布的基于`Protocol Buffers`的RPC框架。 这个demo是结合gRPC和cobra编写的简单helloworld程序。 ### 项目目录 ``` . ├── client │   ├── cmd │   │   ├── root.go │   │   ├── say.go │   │   └── version.go │   └── main.go ├── go.mod ├── go.sum ├── protos │   ├── helloworld_grpc.pb.go │   ├── helloworld.pb.go │   └── helloworld.proto └── server ├── cmd │   ├── root.go │   └── start.go └── main.go ``` ### 实例展示 ``` shell script # 服务端 cd demo/server & go build -o server # 开启服务端 ./server start -a localhost -p 50000 # 客户端 cd demo/client & go build -o client # 客户端发送请求 ./client say foo -a localhost -p 50000 ``` 默认ip是localhost,默认端口号是50021 ``` shell script # 服务端运行结果 2022/02/10 18:30:15 server listening at 127.0.0.1:50000 2022/02/10 18:31:01 Received: foo # 客户端运行结果 2022/02/10 18:31:01 Greeting: Hello foo ``` ### 快速入门 #### 安装 首先,需要安装go。go安装过程忽略。当前我用的go版本是 go1.14 linux/amd64。 然后,grpc和cobra的安装方法如下: ```shell script # 安装Protocol buffer编译器(protoc 版本等于或高于3) sudo apt install -y protobuf-compiler protoc --version # 确认编译器版本高于或等于3 # 为protocol编译器安装Go的插件 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 export PATH="$PATH:$(go env GOPATH)/bin" ``` ```shell script # 安装cobra生成器可执行文件以及库及其依赖项 go get -u github.com/spf13/cobra ``` #### gRPC说明 首先编写`protos/helloworld.proto`。.proto文件中可以定义gRPC服务和消息。 ``` protobuf // 协议为proto3 syntax = "proto3"; // go包名 option go_package = "../helloworld"; // 包名 package helloworld; // 定义服务,可定义多个服务,每个服务可多个接口 service Greet { // rpc请求 请求的函数 (发送请求参数) returns (返回响应的参数) rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHelloAgain (HelloRequest) returns (HelloReply) {} } // 定义发送的消息,采用驼峰命名方式 message HelloRequest { // 参数类型 参数名 标识号(不可重复) string name = 1; } // 定义响应的消息 message HelloReply { string message = 1; } ``` 编译这个proto文件,生成 `helloworld/helloworld.pb.go` and `helloworld/helloworld_grpc.pb.go` 文件。 ```shell script protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ protos/helloworld.proto ``` `helloworld/helloworld_grpc.pb.go`文件主要包含服务和消息的定义。`helloworld/helloworld.pb.go`文件主要包含protobuf的基础函数。 ##### 服务器端 文件 `server/cmd/start.go`中,首先要实现grpc的server类。 ```go // 服务端实现的类 type server struct { // gRPC 根据 proto 生成的接口 pb.UnimplementedGreetServer } // gRPC 服务器端服务接口实现, 参数是context.Context,*pb.HelloRequest,返回值是*pb.HelloReply 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 } // gRPC 服务器端服务接口实现,参数是context.Context,*pb.HelloRequest,返回值是*pb.HelloReply func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received again: %v", in.GetName()) return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil } ``` 服务端还需要编写如何开启服务。 ```go // 开启服务端 lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", address, port)) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreetServer(s, &server{}) log.Printf("server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } ``` ##### 客户端 ```go // 设置与服务器的连接。 conn, err := grpc.Dial(address+":"+port, grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreetClient(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()) ``` #### cobra说明 ##### 概念 Cobra 结构由三部分组成:命令 `commands`、参数 `arguments`、标志 `flags`。最好的应用程序在使用时读起来像句子,要遵循的模式是`APPNAME VERB NOUN --ADJECTIVE`,实际语法是`APPNAME COMMAND ARGS --FLAGS`。 ##### cobra init自动生成应用代码 创建初始应用程序代码,用正确的结构填充应用程序。可以在当前目录中运行,也可在指定现有项目的相对路径,如果目录不存在,将会创建。 ```shell script # 在第一次使用cobra时需要加--pkg-name。 cobra init client --pkg-name client cobra init server ``` 执行init命令自动生成如下代码: ```go var rootCmd = &cobra.Command{ Use: "demo", Short: "Go gRPC+cobra implement demo", Long: `The project has both the client and the server. Request hello, and reply hello. This is a demo implemented by Go gRPC+cobra `, } func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) } } func init() { rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } ``` ##### cobra add自动生成命令代码 一旦应用程序初始化后,cobra就可以为您创建其他命令。 ```shell script cd client cobra add version cobra add say ``` 执行add命令自动生成如下代码: ```go var versionCmd = &cobra.Command{ Use: "version", Short: "The project current version", Long: `-- The project current version --`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("version 1.0.0") }, } func init() { rootCmd.AddCommand(versionCmd) } ``` 此时客户端目录结果为: ```shell script . ├── cmd │   ├── root.go │   ├── say.go │   └── version.go └── main.go ``` ##### 使用标志 标志提供修饰符来控制操作命令的操作方式。 为命令指定flags 由于标志是在不同的位置定义和使用的,因此我们需要在外部定义一个具有正确作用域的变量来分配要使用的标志。 ```go var Verbose bool var Source string ``` 持久标志:该标志可用于分配给它的命令以及该命令下的每个命令。 ```go // 命令为root。参数类型是bool。参数: 变量指针,标志全称,标志缩写,默认值,标志说明 rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") ``` 局部标志:只适用于特定命令。 ```go // 命令为用户添加的命令,譬如client的say,server的start,而不是整个语句范围的命令。参数类型是string。参数: 变量指针,标志全称,标志缩写,默认值,标志说明 localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") ``` ##### 参数说明 ```go Run: func(cmd *cobra.Command, args []string) { if len(args) != 0 { // 读取参数args[0]。读取参数的方法是args[0],args[1] ······ name = flag.String("name", args[0], "Name to greet") } else { name = flag.String("name", "default", "Name to greet") } ··· ··· } ``` 也可以使用命令的Args字段操作arguments。 ```go var cmd = &cobra.Command{ Short: "hello", // 这里是在Args字段中,进行命令参数检查。 Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("requires a color argument") } if myapp.IsValidColor(args[0]) { return nil } return fmt.Errorf("invalid color specified: %s", args[0]) }, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Hello, World!") }, } ``` ##### help command 当您有子命令时,Cobra会自动将help命令添加到应用程序中。命令用法和标记的用法都将自动加入的帮助命令中。 ### 参考 https://github.com/spf13/cobra/blob/master/user_guide.md https://grpc.io/docs/what-is-grpc/core-concepts/ https://grpc.io/docs/languages/go/basics/