5
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

Organization

Protocol Buffer: Basics Go (Tutorial)

Protocol Buffer: Basic Go

このチュートリアルはGoプログラマにprotocol buffersの使い方を紹介します。
使われるprotocol buffersのバージョンはproto3です。

簡単なサンプルアプリケーションの作成によって

  • .protoファイル内でのmessageの定義の仕方
  • protocol buffer compilerの使い方
  • Go protocol buffer APIを用いたmessageの読み書きの仕方

を紹介します。

protocol bufferの詳細な情報を知りたい場合は以下へどうぞ
Protocol Buffer Language Guide
Go API Reference
Go Generated Code Guide
Encoding Reference

Why use protocol buffers?

ここで作るサンプルアプリケーションは、連絡先情報の書き込み、読み込みをファイル?との間でできるような簡単な連絡帳アプリケーションです。

それぞれの連絡先情報は
* 名前
* ID
* Email
* 電話番号
の情報を持っています。

これらのデータをシリアライズするには様々な方法があり、例えば
* goのパッケージgobを使う
* XMLを使う
などがあります。

Protocol buffersはこのシリアライズの問題に対して柔軟で, 効率的, 自動的 な解決策を示すことができます。
.protoファイルに保存したいデータを記述するとProtocol bufferのコンパイラは効率的なバイナリ形式でProtocol Bufferデータの自動エンコーディングと解析を実装します。

さらに、Protocol Bufferは以前のフォーマットで符号化されたデータを読み取れるようにフォーマットを拡張していく考え方に元付いて作られています。

Defining your protocol format

サンプルアプリケーションを作っていきましょう。

まず、.protoファイルを作る必要があります。
.protoファイルにはシリアライズしたいデータを追加していきます。
このサンプルではaddressbook.protoを作りましょう

.protoファイルはパッケージの宣言から始まります、これは異なるプロジェクト間のネームスペースの競合を防ぐのに役立ちます。

syntax = "proto3";   // 使うバージョンの指定
package tutorial     // パッケージの宣言

パッケージ名はgo_packageを指定しない限り、goパッケージとして解釈されます。
go_packageを使用する際もネームスペースの競合が起きないようにpackageを宣言する必要があります。

次にメッセージを定義します。
メッセージは型付きのフィールドを含む集合体です。
bool,int32,float,double,stringなど多くの標準的なデータ型をフィールドの型として指定することができます。
他のメッセージタイプをフィールドタイプとして宣言し、メッセージに構造を持たせることも可能です

syntax = "proto3";
package tutorial;


message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

上記のサンプルではPerson messageにはPhoneNumber messageが含まれ、AddressBook messageにはPerson messageが含まれています。
上記のようにネストされた他のメッセージもフィールドタイプとして定義することができます。
さらに、列挙型を定義する事も可能です。

各要素に書かれている『= 1』『= 2』などはフィールドがバイナリで使用する際に一意のタグを識別する為に必要です
もしフィールドの値がセットされていなかった時にはデフォルト(数値型: 0, String: "", bool: false, )が使用されます

フィールドがrepeatedの場合、フィールドを任意の回数繰り返す事ができます。
繰り返される値の順序はProtocol bufferに保存されます。
repeatedフィールドは動的サイズの配列と考えることができます。

.protoファイルの完璧なガイドを見たい場合は次のサイトを参照して見てください。
Protocol Buffer Language Guide

Compiling your protocol buffers

次は先ほど定義した.protoファイルをコンパイルさせ定義したクラスなどを生成させていきます。

これにはprotocコマンドを使います。

  1. protocコマンドがインストールされていない場合はこちらよりダウンロードしてください。
    Download the package

  2. 次のコマンドを入力してGoのprotocol buffersプラグインをインストールしてください。

go get -u github.com/golang/protobuf/protoc-gen-go
  1. コンパイラを実行します。 ソースコードディレクトリ(指定しなかった場合はカレントディレクトリ)、宛先ディレクトリ(生成されたコードをどこに置くかを指定します。 多くの場合$SRC_DIRと同じです)の指定と.protoファイルのパスの指定を行います。
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

ex
|
-addressbook.proto

の場合は

protoc -I=./ --go_out=./ addressbook.proto

のようになります。

The Protocol Buffer API

生成されたaddressbook.pb.goによって
* Peopleフィールドを含んだAddressBook
* Name, Id, Email, Phonesを含んだPerson
* Number, Typeを含んだPerson_PhoneNumber
* enum型のPerson.PhoneTypeを含んだPerson_PhoneType
のような便利な型を使えるようになります。

ここで何を作られているのか詳しく知りたい場合はGo Generated Code guideを呼んでください。

Personインスタンス作成の例は次のような感じです。(Go)

p := pb.Person{
        Id:    1234,
        Name:  "John Doe",
        Email: "jdoe@example.com",
        Phones: []*pb.Person_PhoneNumber{
                {Number: "555-4321", Type: pb.Person_HOME},
        },
}

Writing a Message

protocol buffersを使う目的はデータのシリアライズでした。
GoではprotoライブラリのMarshal関数を使うことによりデータをシリアライズすることができます。
proto.Marshalを呼ぶとシリアライズされたprotocol bufferが帰ってきます。

book := &pb.AddressBook{}
// ...

// Write the new address book back to disk.
out, err := proto.Marshal(book)
if err != nil {
        log.Fatalln("Failed to encode address book:", err)
}
if err := ioutil.WriteFile(fname, out, 0644); err != nil {
        log.Fatalln("Failed to write address book:", err)
}

Reading a Message

エンコードされたメッセージをパースするにはprotoライブラリのUnmarshal関数を使います。
これを呼び出すとbufのデータをprotocol bufferとして解析し、結果をpbに格納することができます。

// Read the existing address book.
in, err := ioutil.ReadFile(fname)
if err != nil {
        log.Fatalln("Error reading file:", err)
}
book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
        log.Fatalln("Failed to parse address book:", err)
}

これでチュートリアルは完了です
お疲れ様でした。

さらに詳しい情報を知りたい方は次のリンクを見てみてください。
Protocol Buffer Language Guide

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
5
Help us understand the problem. What are the problem?