shinobe179の日記

パケットの気持ちになって考えるのはもうやめだ

Protocol Buffersのことを調べてだいたい分かった気になった人のメモ

はじめに

Protocol Buffers(ProtoBuf)という技術があるらしいということを最近知った。データの送受信に関係する技術らしいし、プロトコルなんて言われたらネットワーク屋(やめるけど)として知ってなきゃならないかなと思ってちょっとだけ調べたのでまとめておく。ProtoBufについて本気で調べたくてこの記事に辿り着いた方のことを思うと、本当に申し訳ない。

ProtoBuf is 何

よそではデータシリアライザとかって言葉で紹介されている。シリアライザって言われてもピンと来なかったんだけど、あるフォーマットに基づいたデータを効率よくやりとりするための技術と解釈した。gRPCで使われている。

データフォーマット(スキーマ)の定義 - データの送受信のためのシリアライズ、デシリアライズ

チュートリアル

protocのビルド、インストール

protocコマンドをインストールする。結構時間がかかる。protocコマンドは、後で作るスキーマ定義ファイルから各言語向けのライブラリを生成できる。

$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protobuf-python-3.7.1.tar.gz
$ tar xvzf protobuf-python-3.7.1.tar.gz
$ cd protobuf-3.7.1
$ ./configure
$ make
$ make check
$ sudo make install
$ sudo ldconfig

.protoファイルの作成

やりとりするデータのスキーマ(:=プロトコル)を定義する。文法はよく分ってないけど、フィールドの名前と、ユニークなIDを定義すれば最低限いいっぽい。

$ cat protobuf_chat.proto 
syntax = "proto3";

message Chat {
  string name = 1;
  string content = 2;
  string ps = 3;
}

Python用ライブラリの生成

さっきビルドしたprotocコマンドで、Python用のライブラリを生成する。

$ protoc --python_out ./ protobuf_chat.proto

あとは生成されたライブラリをロードして、データをやりとりするコードを書けばよい。Pythonでは追加で以下のライブラリが必要だった。

$ pip install protobuf
$ pip install google-api-python-client

だいたいの使い方

.protoファイルで定義した通りの構造体+シリアライズ、デシリアライズのためのメソッドがある、という感じ。

# インポート
>>> import protobuf_chat_pb2

# Chatインスタンスを作り、スキーマに沿ってデータを入力
>>> chat = protobuf_chat_pb2.Chat()
>>> chat.name = 'Alice'
>>> chat.content = 'I am Alice.'

# シリアライズ
>>> chat.SerializeToString()
b'\n\x05Alice\x12\x0bI am Alice.'

# Chatインスタンスを作って、シリアライズされた文字列をパースしてインスタンスにデータをロードする
>>> peer = protobuf_chat_pb2.Chat()
>>> peer.ParseFromString(b'\n\x05Alice\x12\x0bI am Alice.')
20
>>> peer.content
'I am Alice.'
>>> 

作ったもの

名前だけで食いついたものの、分散システム作ったりとかしてないので、とりあえずsocketと組み合わせて簡易なチャットを作って供養した。

github.com

送信側は文字列をProtoBufでシリアライズして送信し、受信側ではデシリアライズして表示する。文字列がどのようにシリアライズされているのか分かるようにした。

$ python protobuf_chat_server.py Alice
[*] Ready to listen.
[*] Connected by 127.0.0.1:42670.
Server> I am Alice
raw send data: b'\n\x05Alice\x12\nI am Alice'
raw recieve data: b'\n\x03Bob\x12\x08I am bob'
Client> I am bob
Server> Hello
raw send data: b'\n\x05Alice\x12\x05Hello'
raw recieve data: b'\n\x03Bob\x12\x03bye'
[*] Bye.

こうしてみると、シリアライズされた文字列は可読性が低いものの、圧縮されているので、JSONとかをそのまま送るのと比べて転送効率がいいんだろうな。内部通信向けとも紹介されていたので、そういうことだろう。

参考

ProtoBuf

www.slideshare.net

tech-blog.tsukaby.com

qiita.com

qiita.com

socket

algorithm.joho.info