Erlangのノード間通信をRubyで実装してみる

Erlangのノード間通信を理解するために，Erlangの分散プロトコルを実装してErlangノードと通信してみました．(Erlang Advent Calendar用のネタを思いつかなかったので大分前に職場で話した内容です...)

やりたかったこと：

- Erlang Distribution Protocolについての理解を深める
- Rubyから `gen_server:call/2` や `rpc:call/4` してみる
- Erlang + Ruby + Sinatraで何か作る

# Erlangのノード間通信に必要なもの

Erlangノードに接続してメッセージを送受信するためには以下のものが必要です．

- EPMD (Erlang Port Mapper Daemon) ： DNS的なやつ
- Erlang Distribution Protocol ： ハンドシェイクとメッセージ送受信のプロトコル
- External Term Format ： メッセージのシリアライズの仕様

## EPMD(Erlang Port Mapper Daemon)

あるホストで起動しているErlangノードがlistenしているポート番号を管理するデーモンです．
Erlangを分散モードで起動すると，自動的に起動して4369番ポートで待ち受けています．

プロトコルの仕様はErlangのドキュメントの[Distribution Protocol](http://erlang.org/doc/apps/erts/erl_dist_protocol.html) に書かれています．

TCPで接続してリクエストを送るとレスポンスが返ってくる単純な通信です．認証なども不要で簡単にリクエストを送れます．

- リクエストのメッセージフォーマット：  リクエスト長(16bit) + リクエスト
- リクエストの１バイト目がリクエストの種別になっている

| 種別 | 名前 | 説明 |
|:-:|:-:|:-:|
| 120 | ALIVE2_REQ |  ノード登録  |
| 110 | NAMES_REQ | ノード一覧取得  |
| 115 | STOP_REQ | ノード抹消(切断時に自動削除されるので通常不要) |

（レスポンスは省略）

リクエストの種別の値のルールは良くわかりませんが，おそらくASCIIコードです．

- NAMES の 'n' (110)
- STOP の 's' (115)
- ALIVE2 が 'x' (120) なのは謎です(単に'a'が埋まってたから？)

### ノード一覧取得を取得してみる

１バイトのメッセージ 110(NAMES_REQ) を送ればノードの一覧を取得できます．

適当にErlangを分散モードで起動した状態で，試しにシェルからnetcatで問い合わせてみます．

```bash
$ echo -ne "\0x01\x01\x6e" | nc localhost 4369
 name node1 at port 46337
```

node1 が port 46337で起動していることが分わかりました．

## Erlang Distoribution Protocol

ノード間通信を始めるためのハンドシェイクの方法と，プロセス間のメッセージの送受信の方法を定めているプロトコルです．link/monitorの方法も定めれています．

プロトコルの仕様はErlangのドキュメントの[Distribution Protocol](http://erlang.org/doc/apps/erts/erl_dist_protocol.html) に書かれていますが，足りない情報もあるので https://github.com/erlang/otp/ * /internal_doc あたりにあるドキュメントやErlangのソースを読む必要がありました．

### ハンドシェイク

cookieでの認証もこのときに行われます(cookieが違うとハンドシェイクに失敗するのでメッセージを送信できない)．

- フォーマット： データ長(16bit) + タイプ(8bit) + 本体
- 認証はチャレンジ＆レスポンス
- ハッシュ関数はMD5

![image](https://qiita-image-store.s3.amazonaws.com/0/58515/54cb2a78-ffd9-8e15-07e7-d26bdff3cc1f.png)


### メッセージの送受信

- メッセージ長(32bit) + メッセージ
- ２種類の形式があるが，出来ることは同じ

| 4 Bytes | d Bytes | n Bytes | m Bytes |
|:-:|:-:|:-:|:-:|
| Length = d+n+m | DistributionHeader | ControlMessage | Message |

| 4 Bytes | 1 Bytes | n Bytes | m Bytes |
|:-:|:-:|:-:|:-:|
| Length = 1+n+m  | Type ('p') |  ControlMessage |  Message |

- 前者(DistributionHeader あり)の方は少し複雑だがatomをキャッシュできるためサイズを抑えられる
- ControlMessage と Message は External Binary Format(※後述)形式
- ControlMessage はメッセージの種類と宛先プロセスなどの情報が入っています
- Message はErlangプロセスに送信したいメッセージ

`rpc:call`を呼ぶ場合以下のようなメッセージが送られます

- ControlMessage = {6, FromPid, '', rex} %% 6 : REG_SEND
- Message = {'$gen_call', {FromPid,Tag}, {call, Mod, Fun, Args, user}}

rpcは `rex` という名前のgen_serverが処理するので，そのプロセスにメッセージを送っています．


## External Binary Format

`term_to_binary/1`, `binary_to_term/1` でお馴染みのバイナリフォーマットです．

ドキュメントの[External Term Format](http://www.erlang.org/doc/apps/erts/erl_ext_dist.html)を読めば良いだけですが，型ごとにデータ長の持ち方等も違うので実装する場合は少し面倒くさいです．

１つの型に複数の表現方法が存在する場合がありますが，互換性のために用意されているもので通信相手がErlangならハンドシェイク時のversionとflagsで有効・無効を切り替えられるため，全てに対応する必要はありません．

# Ruby 実装

https://github.com/binzume/erlprocvis/blob/master/erlang.rb

雑な実装であまり実用的ではないですが，サンプルとしてSinatraでWebサーバ立ててブラウザからErlangのノードの情報を見たり，プロセスのlink/monitorの構造をWebGLで表示できるようにしてみました．

## Erlangの関数を呼び出してみる

まずErlangを起動します．

```bash
erl -sname node1 -setcookie testtest
```

irbから上記リポジトリの `erlang.rb` をrequireして呼び出します．

``` ruby
irb
irb(main):001:0> require_relative 'erlang'
=> true
irb(main):002:0> erl = Erlang::Erl.new('rubynode@test', "testtest")
=> ...
irb(main):003:0> erl.nodes
=> ["node1"]
irb(main):004:0> erl.rpc_call(erl.nodes[0], :io, :format, ["Hello!\n"])
=> :ok
irb(main):005:0> erl.eval(erl.nodes[0], "1 + 1.")
=> 2
```

リモートノードでErlangのコードをevalできるのは便利です．個人的に欲しかったので実装しました．

## 対応した型

基本的な型は一通り対応してみました．mapsとかもRubyのHashと相互変換されます．

```ruby
  TYPE_NEW_FLOAT = 70
  TYPE_SMALL_INT = 97
  TYPE_INTEGER   = 98
  TYPE_FLOAT     = 99
  TYPE_ATOM      = 100
  TYPE_PORT      = 102
  TYPE_PID       = 103
  TYPE_SMALL_TUPLE = 104
  TYPE_LARGE_TUPLE = 105
  TYPE_NIL       = 106
  TYPE_STRING    = 107
  TYPE_LIST      = 108
  TYPE_BINARY    = 109
  TYPE_SMALL_BIG = 110
  TYPE_LARGE_BIG = 111
  TYPE_FUN       = 112
  TYPE_NEW_REF   = 114
  TYPE_MAP       = 116
```

## WebGLでプロセスを表示してみる

この記事の趣旨とはあまり関係が無い上に何かの役に立つわけでもないものですが，せっかくRubyとErlang間で色々出来るので簡単なWebアプリを作ってみました(普通にErlangで書けば良いだろうと突っ込まれそうですが)

https://github.com/binzume/erlprocvis

![procvis.png](https://qiita-image-store.s3.amazonaws.com/0/58515/b90e6b82-c759-38b5-e9e4-6fe29a8f05ed.png)

表示サンプル(リンク先は静的ページなのでプロセス殺したりは出来ません)：

- Cowboy http://binzume.github.io/erlprocvis/public/procs.html?procs=sample.json
- RabbitMQ http://binzume.github.io/erlprocvis/public/procs.html?procs=sample-mq.json

色々なErlangアプリケーションを起動してプロセスのlinkの関係を見ているだけでもけっこう面白いです．

# 参考にしたもの

- http://www.erlang.org/doc/
 - 重要な部分が省略されていたりするので，分からないことがあったら internal_doc を探すのが良い
- https://github.com/erlang/otp/ * /internal_doc にあるドキュメント
- [Akka と Erlang を Reactive に組み合わせるためのライブラリを作りました](http://qiita.com/okumin/items/e01fce9ec4c71f6601e8)(okumin)