RPC, gRPC のTCPパケット一覧

# RPC
ここではXML-RPCを試した。サーバ、クライアントでrequest/responseをしただけの通信。
シンプルなHTTP/1.1通信になっている

## TCP

### request

HTTP/1.1 の POST で 5, 33 の足し算を依頼している
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/01536443-e4a8-9e31-e257-066c41f0b8ae.png)

### response
XMLの中に `<int>38</int>` が見える
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/826c048a-30d3-3ecc-e3e8-95401c6d4a19.png)

## HTTP/1.1 packets
request/responseの2つだけ。シンプル。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/00bcfd21-295b-bad9-ac1d-4c13ea260765.png)


## テスト方法
```py:server.py
from xmlrpc.server import SimpleXMLRPCServer

def add(x, y):
    return x + y

server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(add, "add")
print("Serving XML-RPC on port 8000...")
server.serve_forever()
```

```py:client.py
import xmlrpc.client

proxy = xmlrpc.client.ServerProxy("http://localhost:8000/")
result = proxy.add(5, 33)
print(f"The result of 5 + 3 is: {result}")
```



# gRPC
HTTP/2 を使ってrequest/responseが行き来した。windows sizeの調整が頻繁に行われ、pingも飛び交っている

## TCP
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/1c4eed10-9fc9-e6da-c9e1-ca7468267e27.png)

## HTTP/2

HTTP/1.1に比べて多くのやり取りがある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/46e341aa-012f-7d05-fc65-fcccb3aadfed.png)

## POST している TCP packet

POST 通信についていうと、HTTP/1.1は1つのパケットの中にheaderもbodyもあったけど、HTTP/2は分かれてい流みたい。まず HEADERS パケットを送り、ここで POST /<path> に送ることを宣言する。直後の WINDOW_UPDATE を挟んで、DATA パケットで body を送信。その直後も WINDOW_UPDATE だ。windowの位置を細かにやりとりすることで効率的なネットワーク利用をしているらしい。

このTCP packet一つの中に、5つのHTTP/2 packetが入っている。
```rb
HyperText Transfer Protocol 2
    Stream: SETTINGS, Stream ID: 0, Length 0
HyperText Transfer Protocol 2
    Stream: HEADERS, Stream ID: 1, Length 204, POST /Greeter/SayHello
HyperText Transfer Protocol 2
    Stream: WINDOW_UPDATE, Stream ID: 1, Length 4
HyperText Transfer Protocol 2
    Stream: DATA, Stream ID: 1, Length 12
HyperText Transfer Protocol 2
    Stream: WINDOW_UPDATE, Stream ID: 0, Length 4
```

そして `Stream: DATA` の中にはGRPC Messageがあり、さらにその中には Protocol Buffers が入っている

```rb
GRPC Message: /Greeter/SayHello, Request
    Message Data: 7 bytes
Protocol Buffers: /Greeter/SayHello,request
            Value: 576f726c64
```

`HTTP/2-DATA( GRPC Message ( Protocol Buffers ) )` という感じの構造だ

`Value: 576f726c64` がbodyの実態、 `World` という文字列だ。サーバはそれを使って `Hello World` を返却する関数になってる。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/e85974e8-2a90-e67b-00eb-cb8776685f9f.png)


gRPCのヘッダの前に入っている SETTINGS[0] とその後の WINDOW_UPDATE[1] が (HTTP/2 を知らない自分には) 特徴的だ。勉強が必要
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/83f98c29-9ea1-20ac-7c8c-6f8c029bbd95.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/f340ac37-98e2-06f0-c84b-2da2f5a747ff.png)

## gRPCが通信を削減するのか試す

Clientから `User(name="John Doe", age=30)` というオブジェクトを送信する。

先にresponseから見ていく。Responseはそのままのtextデータが含まれている(実質binaryかもしれない）。`User Name: John Doe, Age: 30` は28文字であり、value length も 28 bytesとなっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/fb938521-ed30-581c-04f7-b7c32a70c8be.png)

一方requestは、たった12 bytesしかない。切り詰めて`name=John Doe` `age=30` にしても19文字のはずだ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/1ac7b196-9dc9-a9e0-9e0c-c09d244c0af6.png)


```rb
Value: 0a084a6f686e20446f65101e
```

この内容はこうだ。

> Value は次のように解釈できます：
> 
> `0a` → name フィールド
> `08` → name の長さ
> `6a6f686e20446f65` → John Doe
> `10` → age フィールド
> `1e` → age の値（30）

辞書のkeyが `0a`, `10` に短縮されている点が大きい。送受信側が共にobject構造を知っているためkeyを文字列でやり取りする必要がないのだ。
さらに、binary=hexになっている点も大きい。例えば `30` をascii文字で送る場合は2文字必要なので2byte必要だ。具体的には `0x33=3`, `0x30=0` となるため `30` を送るために `0x3330` の2bytesが必要になる。一方、`0x1e` は数字の `30` を表す(16+14だね)。これなら1byteでいい。

これがprotobufの通信量削減だ。


## how to test gRPC - text

```rb:requirements.txt
grpcio-tools
```

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

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
```


```sh: execute
$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto
```
この２ファイルができる
```rb
$ ll *py
-rw-r--r--@ 1 user  staff  1574 Oct 19 21:47 helloworld_pb2.py
-rw-r--r--@ 1 user  staff  3340 Oct 19 21:47 helloworld_pb2_grpc.py
```

```py:server.py
import grpc
from concurrent import futures
import time

# 生成されたファイルをインポート
import helloworld_pb2
import helloworld_pb2_grpc

# Greeterサービスを実装
class GreeterServicer(helloworld_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        response = helloworld_pb2.HelloReply()
        response.message = f"Hello, {request.name}!"
        return response

# サーバーを開始する関数
def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
    server.add_insecure_port('[::]:50051')
    print("Server is starting on port 50051...")
    server.start()
    try:
        while True:
            time.sleep(86400)
    except KeyboardInterrupt:
        server.stop(0)
        print("Server stopped.")

if __name__ == '__main__':
    serve()
```

```py:client.py
import xmlrpc.client

proxy = xmlrpc.client.ServerProxy("http://localhost:8000/")
result = proxy.add(5, 33)
print(f"The result of 5 + 3 is: {result}")
```



## how to test gRPC - Dictionary

```rb:requirements.txt
grpcio-tools
```

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

package dictionary;

// User メッセージの定義
message User {
    string name = 1;
    int32 age = 2;
}

// リクエストとレスポンスメッセージの定義
message DictionaryRequest {
    User user = 1;
}

message DictionaryResponse {
    string message = 1;
}

// サービスの定義
service DictionaryService {
    rpc GetUserInfo(DictionaryRequest) returns (DictionaryResponse);
}
```


```sh: execute
$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto
```
この２ファイルができる
```rb
$ ll *py
-rw-r--r--@ 1 user  staff  1574 Oct 19 21:47 helloworld_pb2.py
-rw-r--r--@ 1 user  staff  3340 Oct 19 21:47 helloworld_pb2_grpc.py
```

```py:server.py
import grpc
from concurrent import futures
import time

import dictionary_pb2
import dictionary_pb2_grpc

class DictionaryService(dictionary_pb2_grpc.DictionaryServiceServicer):
    def GetUserInfo(self, request, context):
        user = request.user
        message = f"User Name: {user.name}, Age: {user.age}"
        return dictionary_pb2.DictionaryResponse(message=message)

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    dictionary_pb2_grpc.add_DictionaryServiceServicer_to_server(DictionaryService(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    print("Server is running on port 50051...")
    try:
        while True:
            time.sleep(86400)  # 1 day
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()
```

```py:client.py
import grpc

import dictionary_pb2
import dictionary_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = dictionary_pb2_grpc.DictionaryServiceStub(channel)
        user = dictionary_pb2.User(name="John Doe", age=30)
        request = dictionary_pb2.DictionaryRequest(user=user)
        response = stub.GetUserInfo(request)
        print(response.message)

if __name__ == '__main__':
    run()
```