Erlangに限らず、TCPソケットを用いた通信にはある種の手順が存在します。
それは
というものです。
この通信をErlangで実行するために使われるモジュールが gen_tcp です。
この章では上記のステップを順番に見ていくことにより、初歩的な gen_tcp の利用を学びます。
利用するプログラムと、その実行結果を記載します。通信の手順を出力するためのコードが含まれるので
少し長くなっていますが、根幹部分はあまり多くありません。
以下で順番に手順を確認していきます。
-module(gt_test).
-export([client/0,server/0]).
client() ->
io:format("C: open connection~n",[]),
SomeHostInNet = "localhost", % to make it runnable on one machine
{ok, Sock} = gen_tcp:connect(SomeHostInNet, 5678,
[binary, {packet, 0}]),
receive
{tcp, Sock, Bin} ->
io:format("C: get ~p~n",[Bin])
end,
ok = gen_tcp:send(Sock, "hello "),
io:format("C: send data~n",[]),
ok = gen_tcp:send(Sock, "erlang"),
io:format("C: send data~n",[]),
ok = gen_tcp:close(Sock),
io:format("C: close~n",[]).
server() ->
{ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0}]),
io:format("S: waiting connection~n",[]),
{ok, Sock} = gen_tcp:accept(LSock),
io:format("S: connected~n",[]),
gen_tcp:send(Sock,"ack"),
{ok, Bin} = do_recv(Sock, []),
ok = gen_tcp:close(Sock),
io:format("S: close~n",[]),
io:format("S: get data~p~n",[Bin]).
do_recv(Sock, BinList) ->
receive
{tcp, Sock, Bin} ->
io:format("S: receive data~n",[]),
do_recv(Sock, [Bin | BinList]);
{tcp_closed, Sock} ->
{ok, lists:reverse(BinList)}
end.
1> spawn(gt_test,server,[]).
<0.33.0>
S: waiting connection
2> gt_test:client().
C: open connection
S: connected
C: get <<"ack">>
C: send data
S: receive data
C: send data
S: receive data
C: close
S: close
ok
S: get data[<<"hello ">>,<<"erlang">>]
まず最初の接続を作るためには、サーバが存在する必要があります。
通信を受け取る先が存在しないと、クライアントは接続を行なうことが出来ないからです。
プログラム中において、サーバが通信を待つ部分は 関数server() の最初の部分です。
{ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0}]),
この関数の返り値であるListenSocketを使って、クライアントとのコネクションを開きます。
次の部分を見てみましょう。
{ok, Sock} = gen_tcp:accept(LSock)
この 関数accept() が実行された時、サーバは初めてクライアントと通信を行なえる状態になります。
クライアントからサーバに対して接続を行ないます。
以下のコードがプログラムが該当部分です。
SomeHostInNet = "localhost", % to make it runnable on one machine
{ok, Sock} = gen_tcp:connect(SomeHostInNet, 5678,
[binary, {packet, 0}]),
今回は localhost 、つまり自分のマシンにたいして接続しています。 他のマシンに接続する場合も同じ要領で接続することが可能です。
この関数を使って、サーバにたいして接続をおこないました。
ここまでの流れを実行結果より確認してみます。
1> spawn(gt_test,server,[]).
<0.33.0>
S: waiting connection
2> gt_test:client().
C: open connection
S: connected
サーバがコネクションを確認した後に、クライアントに対して接続確認のメッセージを送信しています。
server() の以下の部分です。
gen_tcp:send(Sock,"ack")
クライアントはサーバから届けられたメッセージを以下の部分で受け取っています。
receive
{tcp, Sock, Bin} ->
io:format("C: get ~p~n",[Bin])
end,
{tcp, Sock, Bin}にパターンマッチされた結果である Bin をユーザが表示しています。
C: get <<"ack">>
関数sendにより送信されるメッセージのタイプには以下の3種類が存在します。
通常のメッセージ
{tcp, Socket, Data}
ソケットが閉じられた際に送られるメッセージ
{tcp_closed, Socket}
エラーが起きた際に送られるメッセージ
{tcp_error, Socket, Reason}
それでは、実際に必要とされるメッセージをやり取りしている部分を見てみます。 今回はクライアントが2つのメッセージを送信し、サーバがそれを受け取っています。
ok = gen_tcp:send(Sock, "hello "),
io:format("C: send data~n",[]),
ok = gen_tcp:send(Sock, "erlang"),
io:format("C: send data~n",[]),
サーバは以下の部分で受け取っています。
server() ->
....
{ok, Bin} = do_recv(Sock, []),
do_recv(Sock, BinList) ->
receive
{tcp, Sock, Bin} ->
io:format("S: receive data~n",[]),
do_recv(Sock, [Bin | BinList]);
{tcp_closed, Sock} ->
{ok, lists:reverse(BinList)}
end.
今回は 再帰関数do_recv を使って 関数server に連続したメッセージを読み取らせています。
再帰関数 do_recv では受け取ったメッセージをリストとして格納しています。リストの先頭に追加していき、
最後にリストを反転させています。
リストの節でも扱いましたが、これはリストのヘッドには高速にアクセスできるという特性を生かしているためです。
ここまでは実行結果の以下の部分です。
C: send data
S: receive data
C: send data
S: receive data
必要なメッセージを送り終えたクライアントが切断処理を行なっています。
以下の部分です。
ok = gen_tcp:close(Sock)
この関数を呼び出したことにより、サーバとの接続が断たれました。
接続を切る際に相手側(今回はサーバ)にたいして、以下のメッセージが送信されます。
{tcp_closed, Socket}
相手側であるサーバーはこの形式のメッセージを受け取ることにより、接続の解除を知ります。
関数 do_recv 内でこのメッセージを処理しています。
do_recv(Sock, BinList) ->
.....
{tcp_closed, Sock} ->
{ok, lists:reverse(BinList)}
end.
再帰関数が終了していますので、この関数の呼び出し元に値が返されます。
{ok, Bin} = do_recv(Sock, []),
ok = gen_tcp:close(Sock),
io:format("S: close~n",[]),
ここまでの実行結果は以下の部分です。
C: close
S: close
ok
以上で通信は完全に終了しました。
最後にサーバがクライアントから受け取った値を表示してみます。
S: get data[<<"hello ">>,<<"erlang">>]
バイナリになっていますが、送信データはきちんと受け取られているようです。