スポンサーリンク

2016年4月18日

Using gen_tcp module in elixir

とある錬金術師の万能薬(Elixir)

Goal

  • gen_tcpの簡単なサンプルを実装する

Dev-Environment

  • OS: Windows8.1
  • Erlang: Eshell V7.3, OTP-Version 18.3
  • Elixir: v1.2.3

Using gen_tcp module in elixir

gen_tcpを使ってみたいと思います。
pingと文字列を渡して、pongと文字列を返すだけの簡単なサンプルの実装になります。
(ローカルホスト内で行っています)

gen_tcp module sample

みんな大好き(?)tcpを使ってサーバとクライアントで通信を取ってみましょう。
全体の処理における流れは以下のようになります。
  1. [server]:gen_tcp.listen/2を呼び出し、1234ポートで待ち受けする。(メッセージなどの仕様に関する取り決めはここで設定)
  2. [server]:gen_tcp.accept/1を呼び出す。ここでクライアントが接続するまで待機する。
  3. [client]:gen_tcp.connect/3でサーバへ接続する。
  4. [server]:gen_tcp.close/1でソケットを閉じる。(別になくてもいい。ソケットの制御プロセスが死ぬとソケットも閉じるため)
  5. [client]:gen_tcp.send/2でサーバ側へデータを送る。(送信データはバイナリ)
  6. [server]クライアント側からの送信データを受信し処理する。
  7. [server]:gen_tcp.send/2でクライアント側へデータを送る。
  8. [client]サーバ側からの返答を受信し処理する。
  9. [client]:gen_tcp.close/1でソケットを閉じる。
ざっと処理を書いていくとこんな感じになります。
ちなみに、データの送受信はプロセスと同じやり方で送受信できるので、かなり分かりやすいです。
サーバの実装は以下のようになります。

File: simple_tcp_server.ex

defmodule SimpleTcpServer do
  def start_server do
    {:ok, listen} = :gen_tcp.listen(1234, [:binary, {:packet, 4},
                                                    {:reuseaddr, :true},
                                                    {:active, :true}])
    {:ok, _socket} = :gen_tcp.accept(listen)
    :gen_tcp.close(listen)
    loop
  end

  defp loop do
    receive do
      {:tcp, socket, bin} ->
        :io.format("Server receive binary = ~p~n", [bin])
        str = :erlang.binary_to_term(bin)
        :io.format("Server (unpacked) ~p~n", [str])
        :gen_tcp.send(socket, :erlang.term_to_binary('pong'))
        loop
      {:tcp_closed, _socket} ->
        IO.puts "Server socket closed"
    end
  end
end
次は、クライアントの実装になります。

File: simple_tcp_client.ex

defmodule SimpleTcpClient do
  def ping do
    {:ok, socket} = :gen_tcp.connect(String.to_char_list("localhost"), 1234,
                                     [:binary, {:packet, 4}])

    :ok = :gen_tcp.send(socket,
                        String.to_char_list("ping") |> :erlang.term_to_binary)

    receive do
      {:tcp, socket, bin} ->
        :io.format("Client received binary = ~p~n", [bin])
        value = :erlang.binary_to_term(bin)
        :io.format("Client result = ~p~n", [value])
        :gen_tcp.close(socket)
    end
  end
end
注意点としては、バイナリでやり取りしているのでバイナリへ変換していることと、
stringではなく、char_listで送っている点です。
実行してみましょう。ターミナルが二つ必要になります。

Example:

  • ターミナル1(Server)
iex> SimpleTcpServer.start_server
...

# ターミナル2を実行すると下記のように出力される
Server receive binary = <<131,107,0,4,112,105,110,103>>
Server unpacked = "ping"
Server socket closed
:ok
  • ターミナル2(Client)
iex> SimpleTcpClient.ping
Client received binary = <<131,107,0,4,112,111,110,103>>
Client result = "pong"
:ok

Note:

gen_tcp使うなら、Erlangのinetモジュールも覚えておいて損はないと思う。
TCP/IPプロトコルのアクセスを行えるモジュールのようなので、アクセス元のIPアドレスとか色々情報引っ張れる。

Bibliography

人気の投稿