0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RPC, gRPC のTCPパケット一覧

Last updated at Posted at 2024-10-19

RPC

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

TCP

request

HTTP/1.1 の POST で 5, 33 の足し算を依頼している
image.png

response

XMLの中に <int>38</int> が見える
image.png

HTTP/1.1 packets

request/responseの2つだけ。シンプル。
image.png

テスト方法

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

HTTP/2

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

image.png

POST している TCP packet

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

このTCP packet一つの中に、5つのHTTP/2 packetが入っている。

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 が入っている

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

gRPCのヘッダの前に入っている SETTINGS[0] とその後の WINDOW_UPDATE[1] が (HTTP/2 を知らない自分には) 特徴的だ。勉強が必要
image.png
image.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

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

image.png

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

requirements.txt
grpcio-tools
helloworld.proto
syntax = "proto3";

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

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
execute
$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto

この2ファイルができる

$ 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
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()
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

requirements.txt
grpcio-tools
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);
}
execute
$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto

この2ファイルができる

$ 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
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()
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()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?