GoでgRPCのクライアントストリーミングをする

## はじめに
gRPCでクライアントストリーミングするサンプルです．

サンプルコード内でところどころモジュール名`github.com/Tsuyopon-1067/grpc-client-streaming-test`が登場しますがよしなに読み替えてください．

このサンプルでは以下のような通信をします．

<div class="image-container">
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644068/bc7497c2-48f9-aa35-d20c-82511d0e7de8.png" width=420px alt="通信のイメージ"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644068/fd0debd3-d735-edca-f99c-219b834dc30e.png" width=250px alt="通信するデータ">
</div>

[このサンプルのGitHubリポジトリ](https://github.com/Tsuyopon-1067/grpc-client-streaming-test)

## 準備

Go プラグインをインストール
```bash
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```

Protocol Buffers をインストール．
```bash
$ brew install protobuf
```

好きな作業用ディレクトリも用意しておいてください．例ではコマンドでディレクトリを作成していますがもちろんGUIで作っても問題ありません．

```bash
mkdir hogehoge
```

Goのモジュールを作成
```bash
$ go mod init <好きなモジュール名>
```

この先の例は次のコマンドでモジュールを作った前提です．別の名前のモジュールを作った場合はその名前に読み替えてください（つまりコピペオンリーだと動かない）．
```bash
$ go mod init github.com/Tsuyopon-1067/grpc-client-streaming-test
```

## プログラム

最終的にはこのようなディレクトリになります．
```
.
├── client
│   └── main.go
├── go.mod
├── go.sum
├── scantext
│   ├── scantext.pb.go
│   ├── scantext.proto
│   └── scantext_grpc.pb.go
└── server
    └── main.go
```


### proto

スキーマを以下のように定義します．ファイルは上のディレクトリ図の場所に作成します．

```go:scantext/scantext.proto
syntax = "proto3";

option go_package = "github.com/Tsuyopon-1067/grpc-client-streaming-test/scantext";

package scantext;

import "google/protobuf/empty.proto";

service Sender {
  rpc SendText(ScanText) returns (google.protobuf.Empty) {}
}

message ScanText { string content = 1; }
```

以下のコマンドを`scantext`の外（後で作る`server`や`client`と同じディレクトリ）で実行してgoファイルを生成します．

```bash
$ protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    scantext/scantext.proto
```

<details><summary>開くとコード生成コマンドの意味が見れます</summary>


- `--go_out=`
    - goコード生成
- `.`
    - 出力先はカレントディレクトリ
- `--go_opt=`
    - コード生成オプション
- `paths=source_relative`
    - 生成されるファイルのパスを`.proto`ファイルからの相対パスにする
- `--go-grpc_out=`
    - gRPC用のGoコードを生成する
- `.`
    - 出力先はカレントディレクトリ
- `-go-grpc_opt=`
    - gRPC Go コードの生成オプション
- `paths=source_relative`
    - 生成されるファイルのパスを`.proto`ファイルからの相対パスにする
- `scantext/scantext.proto`
    - `.proto`ファイルの場所

</details>

<br />

わざわざ通常のgo用コードとgRPC用のコード両方を生成しているので行かのようにgRPC用のコードだけ生成すれば良さそうに見えますが，通常のGoコードも使うのでgRPC用のコードの生成だけでは足りません．

```bash
$ protoc --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    scantext/scantext.proto
```
↑これは実行しないので注意（多分実行しても悪いことは起きないけど）

### サーバ

サーバのコードを以下のように書きます．ファイルは上のディレクトリ図の場所に作成します．

```go:server/main.go
package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"github.com/Tsuyopon-1067/grpc-client-streaming-test/scantext"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/emptypb"
)

type server struct {
	scantext.UnimplementedSenderServer
}

// クライアントから呼び出される関数
func (s *server) SendText(ctx context.Context, in *scantext.ScanText) (*emptypb.Empty, error) {
	log.Printf("Received: %v", in.Content)
	return &emptypb.Empty{}, nil
}

func main() {
	lis, err := net.Listen("tcp", ":8080") // 接続を待ち受ける
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}
	s := grpc.NewServer()                       // gRPCサーバーを作成
	scantext.RegisterSenderServer(s, &server{}) // サーバーにサービスを登録
	fmt.Println("Server is running on :8080")
	if err := s.Serve(lis); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}

```

### クライアント

クライアントのコードを以下のように書きます．ファイルは上のディレクトリ図の場所に作成します．

```go:client/main.go
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Tsuyopon-1067/grpc-client-streaming-test/scantext"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// grpc.WithInsecure() を指定するとTLSで暗号化を行わずに通信する
	conn, err := grpc.NewClient("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("Failed to connect: %v", err)
	}
	defer conn.Close() // main関数終了時に接続を閉じる

	client := scantext.NewSenderClient(conn) // クライアントを作成

	for {
		var text string                           // 標準入力受取用
		fmt.Println("Enter text: (quit to exit)") // ただの指示
		fmt.Scan(&text)                           // 標準入力を受け取る
		if text == "quit" {                       // quit と入力されたらループを抜けて終了
			break
		}

		scanText := scantext.ScanText{ // 送信するメッセージを作成
			Content: text,
		}

		_, err := client.SendText(context.Background(), &scanText) // メッセージを送信
		if err != nil {                                            // エラーが発生したらログ出力して終了
			log.Fatalf("Error sending message: %v", err)
		}

		fmt.Printf("Sent message: %s\n", text) // ただのログ出力
	}

	fmt.Println("All messages sent successfully")
}

```

### `ScanText`関数について
サーバ・クライントどちらでも`ScanText`関数が呼び出されています．しかし，これらは別物です（関数のとる引数やバインドされる先が異なる）．クライアントで`ScanText`関数を実行すると，サーバで別の`ScanText`関数が何者かに実行されるイメージです．

## 実行

1. ターミナルを2つ起動する（それぞれターミナル1，ターミナル2と呼ぶ）．
1. どちらかのターミナルで以下のコマンドを実行（依存関係の追加）．
    ```bash
    $ go mod tidy
    ```
1. ターミナル1では以下のコマンドを実行（サーバを立ち上げる）．
    ```bash
    $ go run ./server/main.go
    ```
1. ターミナル2では以下のコマンドを実行（クライアントを立ち上げる）．
    ```bash
    $ go run ./client/main.go
    ```
1. ターミナル2に好きなメッセージを入力し，Enterを押して送信する．そして，ターミナル1にメッセージが届いていることを確認する．
1. 気が済んだらターミナル1ではCtrl+Cを，ターミナル2では"quit"と入力してプログラムを終了する．

- サーバの出力例
```bash
$ go run ./server/main.go
Server is running on :8080
2024/09/26 17:25:47 Received: message
2024/09/26 17:25:47 Received: 1
2024/09/26 17:25:50 Received: message
2024/09/26 17:25:50 Received: 2
2024/09/26 17:25:52 Received: message
2024/09/26 17:25:52 Received: 3
2024/09/26 17:25:53 Received: hoge
2024/09/26 17:25:54 Received: fuga
2024/09/26 17:25:55 Received: piyo
```

- クライアントの出力例
```bash
$ go run ./client/main.go
Enter text: (quit to exit)
message 1
Sent message: message
Enter text: (quit to exit)
Sent message: 1
Enter text: (quit to exit)
message 2
Sent message: message
Enter text: (quit to exit)
Sent message: 2
Enter text: (quit to exit)
message 3
Sent message: message
Enter text: (quit to exit)
Sent message: 3
Enter text: (quit to exit)
hoge
Sent message: hoge
Enter text: (quit to exit)
fuga
Sent message: fuga
Enter text: (quit to exit)
piyo
Sent message: piyo
Enter text: (quit to exit)
quit
All messages sent successfully
```