gRPCで二分木を送信する[Go]

# インストール

必要なツールをインストールする。

```terminal
PROTOC_ZIP=protoc-3.7.1-linux-x86_64.zip
curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/$PROTOC_ZIP
sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
sudo unzip -o $PROTOC_ZIP -d /usr/local 'include/*'
rm -f $PROTOC_ZIP
```

プロジェクトを初期化しgo.modを作る。

```terminal
go mod init
```

```terminal
go get google.golang.org/protobuf/cmd/protoc-gen-go \
         google.golang.org/grpc/cmd/protoc-gen-go-grpc
```

上でprotoc-gen-go-grpcがインストールできなかったら下記を実行

```terminal
go get -u -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@master
```

go.modに各ライブラリが追加されている。  

```mod:go.mod
module github.com/Asuha-a/grpc-go-test

go 1.15

require (
    google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0 // indirect
    google.golang.org/protobuf v1.25.0 // indirect
)
```

# protoファイルの作成
pbディレクトリに入れておく。

```terminal
$ tree
.
├── go.mod
├── go.sum
├── Makefile
└── pb
    └── node.proto


```

```proto:node.proto
syntax = "proto3";

option go_package = "github.com/Asuha-a/go-qiita/pb";

package node;

service Node {
  rpc GetBinaryTree (GetBinaryTreeRequest) returns (GetBinaryTreeReply) {}
}

message node {
  int32 index = 1;
  node right = 2;
  node left = 3;
}

message GetBinaryTreeRequest {
  int32 id = 1;
}

message GetBinaryTreeReply {
  int32 id = 1;
  node root = 2;
}
```

上記のように`node`のmessageを再帰的に呼び出すことで二分木を定義する。

次のコマンドでpbファイルを作成できる。

```terminal
protoc --go_out=. --go_opt=paths=source_relative \
	--go-grpc_out=. --go-grpc_opt=paths=source_relative \
	pb/node.proto
```

いちいち打つのは面倒なのでMakefileを作る。

```Makefile:Makefile
.PHONY: setup
setup:
	protoc --go_out=. --go_opt=paths=source_relative \
		--go-grpc_out=. --go-grpc_opt=paths=source_relative \
		pb/node.proto

.PHONY: clean
clean:
	rm -f pb/node.pb.go
	rm -f pb/node_grpc.pb.go

.PHONY: help
help:
	@echo "setup: setup the envs"
	@echo "clean: delete all files created by make"
```

コマンドを実行

```terminal
make setup
```


```terminal
protoc-gen-go: program not found or is not executable
```

が表示された場合は以下を.bashrcもしくは.zshrcに追加してみてほしい。

```terminal
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOROOT:$GOPATH:$GOBIN
```

# サーバの作成

```go:server.go
package main

import (
	"context"
	"log"
	"net"

	"github.com/Asuha-a/go-qiita/pb"
	"google.golang.org/grpc"
)

const (
	port = ":50051"
)

type server struct {
	pb.UnimplementedNodeServer
}

func (s *server) GetBinaryTree(ctx context.Context, in *pb.GetBinaryTreeRequest) (*pb.GetBinaryTreeReply, error) {
	id := in.Id
	data := &pb.Node{
		Index: 5,
		Right: &pb.Node{Index: 7, Right: nil, Left: nil},
		Left:  &pb.Node{Index: 3, Right: nil, Left: nil},
	}
	return &pb.GetBinaryTreeReply{Id: id, Root: data}, nil
}

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterNodeServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
```

# クライアントの作成

```go:client.go
package main

import (
	"context"
	"log"
	"time"

	"github.com/Asuha-a/go-qiita/pb"
	"google.golang.org/grpc"
)

const (
	port = ":50051"
)

func main() {
	conn, err := grpc.Dial(port, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	client := pb.NewNodeClient(conn)

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := client.GetBinaryTree(ctx, &pb.GetBinaryTreeRequest{
		Id: 1,
	})
	if err != nil {
		panic(err)
	}
	log.Printf("%v", r.Root)
}
```

# 完成図

```terminal
$ tree
.
├── client
│   └── client.go
├── go.mod
├── go.sum
├── Makefile
├── pb
│   ├── node_grpc.pb.go
│   ├── node.pb.go
│   └── node.proto
└── server
    └── server.go

3 directories, 8 files

```

# 動作検証

サーバを起動したのちにクライアントを実行。

```terminal
$ go run ./server/server.go

```

```terminal
$ go run ./client/client.go                                                                           
2020/12/10 21:09:55 index:5 right:{index:7} left:{index:3} 
```

無事受け取れた。