17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Protocol Buffers動かしてみる

Last updated at Posted at 2017-12-15

初めに

こんにちは。CYBIRDエンジニア Advent Calendar(http://qiita.com/advent-calendar/2017/cybird)
15日目の@keitarou です。14日目は@sakamoto_kojiさんの「Actions on Google(Dialogflow)での構築知見を少し」でした。

Protocol Buffers

「Protocol Buffers」が何かっていうところは、他のネットの情報が豊富なので軽くですが、
単に言うと、バイナリ形式のメッセージシリアライズフォーマット。
主に業務などで利用しているのは、「JSON」はほぼ全てですが、

  • データサイズをできるだけ小さくしたい!
  • エンコード・デコードをできるだけ高速に省メモリで!
  • gRPCとか、RPCのフレームワークを使ってマイクロサービスしたい!

など、そのへんの話をされた時に困らないように、簡単なデモを動かしての予習です。

その他、バイナリ形式の同様技術としては

  • Flatbuffers
  • Apache Thrift
  • MessagePack
  • BSON

などもある模様。

やってみること

    1. ビットコインの取引履歴をprotocol-buffer返すtcpサーバーを立てる
    1. (1)で作ったサーバーへtcpリクエストを送り、protocol-bufferをパースして画面へ出力するクライアントを作る
    1. 手っ取り早く終わらせるために、クライアント・サーバーともにNode.jsで作ることとする

※今回はNode.jsを例で使っていますが、Java, Golang, C#, PHPとかいろんな言語で利用することも勿論できます。
※ビットコインの取引履歴はCoincheckのPublic APIを利用させて頂きます。
https://coincheck.com/ja/documents/exchange/api#public
図で書くとこういった感じです。
スクリーンショット 2017-12-14 20.03.46.png

protocコマンドのインストール

あとに記述しますが、.protoファイルというインタフェース定義言語(IDL)をビルドする必要があります。
ビルドに必要なコマンドをインストールします

brew install protobuf

.protoファイルの作成

https://coincheck.com/api/trades
[
  {
    "id": 60989919,
    "amount": "0.004",
    "rate": 1912722,
    "order_type": "sell",
    "created_at": "2017-12-14T10:14:09.000Z"
  },
  .
  .
  .
  .
  .
  .
]

こんな感じのJSON APIを扱えたら良いのでこんな感じで

messages.proto
syntax = "proto3";

message Trades
{
  enum OrderType
  {
    UNKNOWN = 0;
    BUY     = 1;
    SELL    = 2;
  }

  message Trade
  {
    int32     id        = 1;
    string    amount    = 2;
    int32     rate      = 3;
    OrderType orderType = 4;
    string    createdAt = 5;
  }

  repeated Trade trades = 1;
}

シンタックスについては公式のドキュメントがわかりやすいです。
日本語ではありませんが、それほど難しくはなさそうです。
https://developers.google.com/protocol-buffers/docs/proto3

もっといいのがあるかもしれませんが、jsonから.protoに変換するツールもあったりします。
https://www.site24x7.com/tools/json-to-protobuf.html

.prpotoファイルのビルド

protoc -o ./messages.desc messages.proto

出力されたmessage.descをプログラミングのランタイムで利用することになります。

npmライブラリのインストール

# Protocol Buffersのエンコード/デコードができるように
npm install --save node-protobuf

# CoincheckのAPIを簡単に使うために
npm install --save superagent

サーバーの実装

server.js
const fs       = require('fs');
const net      = require('net');
const request  = require('superagent');
const protobuf = require('node-protobuf');
const messages = new protobuf(fs.readFileSync('./messages.desc'));
const TARGET_APIURL = 'https://coincheck.com/api/trades';

const server = net.createServer((conn) =>
  {
    conn.on('data', () =>
    {
      request
        .get(TARGET_APIURL)
        .end((err, res) =>
        {
          if(!err)
          {
            // シリアライズ用に、snake,Camelの変換および、enumとして扱いたい値の変換など
            let fmtData = {
              trades: res.body.map((d) => {
                return {
                  id        : d.id,
                  amount    : d.amount,
                  rate      : d.rate,
                  orderType : d.order_type.toUpperCase(),
                  createdAt : d.created_at
                }
            })};
            let buf = messages.serialize(fmtData, 'Trades');
            conn.write(buf);
          }
          else
          {
            console.error(err);
            conn.write('E');
          }
        });
    });
    conn.on('close', () =>
    {
      console.log('close connection');
    });
  }
).listen(4000);

★サーバー起動方法

node server.js

クライアントの実装

↑で作ったサーバーを1000ms間隔で叩いて出力するだけです。

client.js
const fs       = require('fs');
const net      = require('net');
const protobuf = require('node-protobuf');
const messages = new protobuf(fs.readFileSync('./messages.desc'));
const client   = new net.Socket();

client.on('data', (data) =>
{
  let buf = new Buffer(data);
  let json = messages.parse(buf, 'Trades');
  json.trades.map(d => { console.log(JSON.stringify(d)); });
});
client.on('close', () =>
{
  console.log('connection close');
});

client.connect('4000', 'localhost', () =>
{
  setInterval(() =>
  {
    client.write('ping');
  }, 1000);
});

★実行方法

node client.js

実行結果

$ node client.js

{"id":60995449,"amount":"0.00452097","rate":1923368,"orderType":"BUY","createdAt":"2017-12-14T10:32:36.000Z"}
{"id":60995448,"amount":"0.00547903","rate":1923192,"orderType":"BUY","createdAt":"2017-12-14T10:32:36.000Z"}
{"id":60995447,"amount":"0.005","rate":1923032,"orderType":"BUY","createdAt":"2017-12-14T10:32:36.000Z"}
{"id":60995446,"amount":"0.005","rate":1923000,"orderType":"SELL","createdAt":"2017-12-14T10:32:35.000Z"}
{"id":60995445,"amount":"0.005","rate":1923006,"orderType":"BUY","createdAt":"2017-12-14T10:32:35.000Z"}
{"id":60995444,"amount":"0.00155723","rate":1923000,"orderType":"SELL","createdAt":"2017-12-14T10:32:35.000Z"}
{"id":60995443,"amount":"0.01","rate":1923005,"orderType":"BUY","createdAt":"2017-12-14T10:32:35.000Z"}
{"id":60995442,"amount":"0.005","rate":1923000,"orderType":"SELL","createdAt":"2017-12-14T10:32:35.000Z"}
{"id":60995441,"amount":"0.005","rate":1923005,"orderType":"BUY","createdAt":"2017-12-14T10:32:34.000Z"}
{"id":60995440,"amount":"0.005","rate":1923000,"orderType":"SELL","createdAt":"2017-12-14T10:32:34.000Z"}
{"id":60995439,"amount":"0.10463106","rate":1923000,"orderType":"SELL","createdAt":"2017-12-14T10:32:34.000Z"}
{"id":60995438,"amount":"23.56338218","rate":1923000,"orderType":"BUY","createdAt":"2017-12-14T10:32:34.000Z"}

それっぽくデータをデコードして出力することが出来ていそうです。

おまけに、telnetで実行するとどんなかんじかですが、こんな感じです。
文字列として扱っている値はわかりますが、数値型や列挙型のものは流石に人間で解釈するのは非常に困難です。

$ telnet localhost 4000

Trying ::1...
Connected to localhost.
Escape character is '^]'.


0.03503u *2017-12-14T10:34:21.000Z
0.016u *2017-12-14T10:34:21.000Z
0.3u *2017-12-14T10:34:21.000Z
0.006u *2017-12-14T10:34:20.000Z
0.01u *2017-12-14T10:34:20.000Z
0.01u *2017-12-14T10:34:20.000Z
0.0251u *2017-12-14T10:34:20.000Z
1
0.15255587 *2017-12-14T10:34:20.000Z
0.01Ҩu *2017-12-14T10:34:20.000Z
0.3u *2017-12-14T10:34:20.000Z
0.006u *2017-12-14T10:34:20.000Z
0.026u *2017-12-14T10:34:20.000Z
0.005ϧu *2017-12-14T10:34:20.000Z
1
0.00460936u *2017-12-14T10:34:20.000Z
0.01 *2017-12-14T10:34:20.000Z
0.01 *2017-12-14T10:34:19.000Z
0.025u *2017-12-14T10:34:17.000Z
0.011u *2017-12-14T10:34:15.000Z
0.005u *2017-12-14T10:34:15.000Z
0.01ߡu *2017-12-14T10:34:15.000Z
0.01 *2017-12-14T10:34:15.000Z
0.01ݢu *2017-12-14T10:34:14.000Z
0.005u *2017-12-14T10:34:14.000Z
1
0.01273603ӡu *2017-12-14T10:34:13.000Z
0.046סu *2017-12-14T10:34:13.000Z
0.002եu *2017-12-14T10:34:13.000Z
0.006٥u *2017-12-14T10:34:13.000Z
0.01٣u *2017-12-14T10:34:13.000Z
1
0.07904541٣u *2017-12-14T10:34:13.000Z
0.0083u *2017-12-14T10:34:13.000Z
1
0.35365459ԥu *2017-12-14T10:34:12.000Z
0.15 *2017-12-14T10:34:12.000Z
0.01ڣu *2017-12-14T10:34:12.000Z
0.006٣u *2017-12-14T10:34:12.000Z
0.03119âu *2017-12-14T10:34:12.000Z
0.4327¢u *2017-12-14T10:34:12.000Z
0.01ݡu *2017-12-14T10:34:12.000Z
1
0.00645541͡u *2017-12-14T10:34:12.000Z
0.01881âu *2017-12-14T10:34:11.000Z
0.01999u *2017-12-14T10:34:11.000Z
0.0042 *2017-12-14T10:34:11.000Z
0.004 *2017-12-14T10:34:11.000Z
0.006u *2017-12-14T10:34:11.000Z
0.02u *2017-12-14T10:34:10.000Z
0.003 *2017-12-14T10:34:10.000Z
0.0054u *2017-12-14T10:34:10.000Z
0.03 *2017-12-14T10:34:07.000Z
0.02u *2017-12-14T10:34:07.000Z
0.0046ȟu *2017-12-14T10:34:07.000Z
0.006 *2017-12-14T10:34:07.000Z
0.011u *2017-12-14T10:34:07.000Z

# 最後に

時間があればJSONとの性能比較とかもしたかったんですが、申し訳ないですがこれにて断念でございます。
特に、UnityやCocos2dxなどのゲーム系の通信周りのチューニングのためにJSONなどのテキストベースのシリアライズ方式から、バイナリ形式にしてみるのもよいのではないでしょうか。
それでは、これにて失礼致します。

CYBIRD エンジニア Advent Calendar(http://qiita.com/advent-calendar/2017/cybird) 明日は、@cy-tatsuya-sakaiさんの「Unity uGUI ボタンのイベントを一か所で受け取る」らしいです!楽しみですね!

17
13
1

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
17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?