Hatena::Grouperlang

lnzntの Erlang 日記 このページをアンテナに追加 RSSフィード

2011年08月14日

いろはメモ - Erlang 編 - 「は」 : ポートによる外部接続

| 10:06 | いろはメモ - Erlang 編 - 「は」 : ポートによる外部接続 - lnzntの Erlang 日記 を含むブックマーク はてなブックマーク - いろはメモ - Erlang 編 - 「は」 : ポートによる外部接続 - lnzntの Erlang 日記 いろはメモ - Erlang 編 - 「は」 : ポートによる外部接続 - lnzntの Erlang 日記 のブックマークコメント

いろはメモ - 初心者の書いたメモです。間違いは随時直していきます。

----

Erlang プロセスは「ポート」という仕組みで Erlang 外部の OSプロセスと通信ができる。

  +- ERTS (Erlang RunTime System) ---+       ****** OS のプロセス ******
  |                                  |       *                         *
  |  +-----------+         |>>>>>>>>>>-->>-->(標準入力)                *
  |  | プロセス  |<------->| ポート  |       *                         *
  |  +-----------+         |<<<<<<<<<<--<<--<(標準出力/標準エラー出力) *
  |                                  |       *                         *
  +----------------------------------+       ***************************
パケット

バイトストリームを扱いやすくするための「パケット」の概念がある。

パケットは、バイト列の前に「長さフィールド」を付加したものである。

BIF による(と思う)が、長さフィールドは 1/2/4 バイト長*1のいずれかである。

長さフィールドは NBO*2エンコーディングされ(ているようであ)る。

例: { packet, 4 } で "hello" をパケット化した場合

      [  0,  0,  0,  5, $h, $e, $l, $l, $o ]

 HEX:   00  00  00  05  68  65  6c  6c  6f
        --------------  ------------------
           長さ(=5)          "hello"

簡単な例で試す。

-module(porttest).    %% porttest.erl
-compile(export_all).

start(Command) ->
    Port = open_port({spawn, Command}, [{packet,4}]), %% open_port() でポートを作る
    io:format("port open: [~w]~n", [Port]),
    loop().

loop() ->
    receive
        {Port, {data, Data}} ->                       %% {Port, {data, Data}} がデータ受信のパターン
            io:format("received: '~p' from [~w]~n", [Data, Port]),
            loop();

        X ->
            io:format("unknown message: [~w]~n", [X]),
            throw('Unknown message received.')
    end.

外部プログラムRuby で作成してみる。(./ext.rb)

#!/usr/bin/env ruby

msg    = "hello"
packet = [msg.length, msg].pack("N A*")  # パケット化。長さフィールドは 4 バイト
                                         # ちなみに、2 バイトなら .pack("n A*")
                                         #           1 バイトなら .pack("C A*")

3.times { $>.write packet }              # パケットを 3 回出力

Ruby スクリプトに実行権を付与するのを忘れずに。

 $ chmod +x ./ext.rb

一応、Ruby スクリプトの出力の HEX ダンプを確認しておく。

$ ./ext.rb | od -c -t x1
0000000  \0  \0  \0 005   h   e   l   l   o  \0  \0  \0 005   h   e   l
         00  00  00  05  68  65  6c  6c  6f  00  00  00  05  68  65  6c
0000020   l   o  \0  \0  \0 005   h   e   l   l   o
         6c  6f  00  00  00  05  68  65  6c  6c  6f
0000033

では、erl で実行。(シェルが動かなくなったら CTLR-G + q で脱出する)

$ erl
...
1> c(porttest).
{ok,porttest}
2> porttest:start("./ext.rb").   %% ./ext.rb を指定
port open: [#Port<0.1955>]
received: '"hello"' from [#Port<0.1955>]
received: '"hello"' from [#Port<0.1955>]
received: '"hello"' from [#Port<0.1955>]
                                 %% CTRL-G 
User switch command
 --> q                           %% q [ENTER] でシェル終了
ポートへの書き出しを試す

(書き出し試すのを忘れていたので)試す。

コマンドは "/bin/cat >/tmp/file" とし、パケット化しないで生ストリームに書き出す。

-module(writetest).
-compile(export_all).

start() ->
    Port = open_port({spawn, "/bin/cat >/tmp/file"}, [stream]), %% stream で生ストリームを指定
    io:format("port open: [~w]~n", [Port]),

    Port ! {self(), {command, "hello, erlang\n"}}. %% 書き出しは Port ! {PidC, {command, Data}}
                                                   %% PidC は接続プロセスの PID

erl で実行。

1> c(writetest.erl).
{ok,writetest}
2> writetest:start().
port open: [#Port<0.1955>]
{<0.35.0>,{command,"hello, erlang\n"}}

/tmp/file に書き出されていることを確認。

$ cat /tmp/file 
hello, erlang
$ od -c -t x1 /tmp/file
0000000   h   e   l   l   o   ,       e   r   l   a   n   g  \n
         68  65  6c  6c  6f  2c  20  65  72  6c  61  6e  67  0a
0000016

/tmp/file は消しときましょう。

----

参考リンク

Erlangリファレンスマニュアル

----

参考書籍

プログラミングErlang

プログラミングErlang

オーム社のページ(サンプルソースダウンロードなど) : Ohmsha | 商品一覧

----

参考URL

*1:この文脈では、1 バイト == 8 ビット(== 1 オクテット)とする

*2:Network Byte Order