インストール

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

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を作る。

go mod init
go get google.golang.org/protobuf/cmd/protoc-gen-go \
         google.golang.org/grpc/cmd/protoc-gen-go-grpc

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

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

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ディレクトリに入れておく。

$ tree
.
├── go.mod
├── go.sum
├── Makefile
└── pb
    └── 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ファイルを作成できる。

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

いちいち打つのは面倒なので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"

コマンドを実行

make setup
protoc-gen-go: program not found or is not executable

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

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

サーバの作成

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)
    }
}

クライアントの作成

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)
}

完成図

$ 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

動作検証

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

$ go run ./server/server.go

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

無事受け取れた。