Erlang World


top > distributed programming > gen_tcp

gen_tcp

ソケット通信の概観

Erlangに限らず、TCPソケットを用いた通信にはある種の手順が存在します。
それは

  1. サーバが接続を待つ
  2. クライアントがサーバに接続する。
  3. 目的のデータをやり取りする通信を行なう。
  4. クライアント、もしくはサーバが接続を断つ。
  5. 相手側も接続を断つ

というものです。

この通信を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}]),
listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}
Port = 0..65535
Options = [Opt] ListenSocket
指定できるオプションは別の節で詳しくやります。今回は
binary: 受け取るデータはバイナリ型
{packet, 0}: 送られたデータを直接受け取る
という指定をしています。
ListenSocketはgen_tcp:accept()でのみ利用できるソケットです。 このソケットがクライアントからのアクセスを待つ役割を持っています。

この関数の返り値であるListenSocketを使って、クライアントとのコネクションを開きます。
次の部分を見てみましょう。

{ok, Sock} = gen_tcp:accept(LSock)
accept(ListenSocket) -> {ok, Socket} | {error, Reason}
accept(ListenSocket, Timeout) -> {ok, Socket} | {error, Reason}
Timeout = int() | infinity
Socket = socket()
この関数はクライアントからのアクセスがあった際に初めて評価されます。 それまではここで処理を中断しています。
クライアントからのアクセスがあると接続を開き、クライアントとの通信に使うソケットを返します。

この 関数accept() が実行された時、サーバは初めてクライアントと通信を行なえる状態になります。

クライアントからサーバへの接続

クライアントからサーバに対して接続を行ないます。
以下のコードがプログラムが該当部分です。

SomeHostInNet = "localhost", % to make it runnable on one machine
{ok, Sock} = gen_tcp:connect(SomeHostInNet, 5678, 
                                     [binary, {packet, 0}]),

今回は localhost 、つまり自分のマシンにたいして接続しています。 他のマシンに接続する場合も同じ要領で接続することが可能です。

connect(Address, Port, Options) -> {ok, Socket} | {error, Reason}
connect(Address, Port, Options, Timeout) -> {ok, Socket} | {error, Reason}
Address = string() | atom() | ip_address()
Port = 0..65535
Options = [Opt]
Timeout = int() | infinity
Socket = socket()
Reason = posix()

オプションは gen_tcp:listenと同じです。

この関数を使って、サーバにたいして接続をおこないました。

ここまでの流れを実行結果より確認してみます。

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")
send(Socket, Packet) -> ok | {error, Reason}
Socket = socket()
Packet = [char()] | binary()
Reason = posix()
ソケットSocketにより接続されている相手に対して、メッセージであるPacketを送信します。 送信のタイムアウトは connection のオプションで設定することが可能です。

クライアントはサーバから届けられたメッセージを以下の部分で受け取っています。

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">>]

バイナリになっていますが、送信データはきちんと受け取られているようです。


Yuichi ITO. All rights reserved.
mail to : ad
inserted by FC2 system