UDPによるソケット間通信はTCPのものよりも単純です。
それは
というものです。
お気づきの方が多いと思いますが、UDPによる通信ではサーバとクライアントのコネクションは作りません。 その点においてTCPよりもUDPのほうが単純だと言えます。
UDPによる通信をErlangで実行するために使われるモジュールが gen_udp です。
この章では上記のステップを順番に見ていくことにより、初歩的な gen_ucp の利用を学びます。
前の節と同じように、順番に手順を確認していきます。
このプログラムはサーバがクライアントからのメッセージを加工して返信するものです。
-module(gu_test).
-export([server/0,client/1]).
-define(SERV_IP,"localhost").
-define(SERV_PORT,5678).
server() ->
{ok, Sock} = gen_udp:open(?SERV_PORT, [binary]),
io:format("S: open port ~p~n",[?SERV_PORT]),
loop(Sock).
loop(Sock) ->
receive
{udp, Sock, Sender_IP, Sender_Port, Packet} ->
io:format("S: recv ~p~n",[Packet]),
NewPacket = list_to_binary([<<"recv: ">>,Packet]),
gen_udp:send(Sock, Sender_IP, Sender_Port, NewPacket),
loop(Sock)
end.
client(Client_Port) ->
{ok, Sock} = gen_udp:open(Client_Port, [binary]),
io:format("C: open port ~p~n",[Client_Port]),
gen_udp:send(Sock, ?SERV_IP, ?SERV_PORT, <<"hello">>),
gen_udp:send(Sock, ?SERV_IP, ?SERV_PORT, <<"erlang">>),
gen_udp:send(Sock, ?SERV_IP, ?SERV_PORT, <<"java">>),
receiver(Sock).
receiver(Sock) ->
receive
{udp, Sock, _IP, _InPortNo, Packet} ->
io:format("C: ~p~n",[Packet]),
receiver(Sock)
after
1000 ->
io:format("good bye~n",[]),
gen_udp:close(Sock)
end.
1> spawn(gu_test,server,[]).
<0.33.0>
S: open port 5678
2> gu_test:client(6789).
C: open port 6789
S: recv <<"hello">>
S: recv <<"erlang">>
C: <<"recv: hello">>
C: <<"recv: erlang">>
S: recv <<"java">>
C: <<"recv: java">>
good bye
TCPによる通信と違いUDPにおいてサーバとクライアントは接続されません。
サーバの所定の場所にメッセージをクライアントが送るという形です。
server() の以下の部分が、サーバのポートを開く部分です。
{ok, Sock} = gen_udp:open(?SERV_PORT, [binary]),
これでサーバが指定されたポートより、UDPメッセージを受け取る準備ができました。
クライアントがポートを開く操作も全く同じです。
以下のコードがプログラムが該当部分です。
{ok, Sock} = gen_udp:open(Client_Port, [binary]),
開かれたポートはメッセージの送信も受信も行なうことができますので、 サーバとクライアントのポートの違いは全くありません。
ここまでの実行結果は以下の部分です。
1> spawn(gu_test,server,[]).
<0.33.0>
S: open port 5678
2> gu_test:client(6789).
C: open port 6789
クライアントがサーバに対してメッセージを送信しているのは以下の部分です。
メッセージの送信に関しては gen_tcp よりも多少複雑です。これは gen_tcp がコネクションを開く際に相手をしていたのに対して、gen_udp は
メッセージを送信する際に相手を指定するためです。
gen_udp:send(Sock, ?SERV_IP, ?SERV_PORT, <<"hello">>),
gen_udp:send(Sock, ?SERV_IP, ?SERV_PORT, <<"erlang">>),
gen_udp:send(Sock, ?SERV_IP, ?SERV_PORT, <<"java">>),
関数sendにより送信されるメッセージは以下の形です。gen_tcpと違い一種類しか形はありません。
{udp, Socket, IP, InPortNo, Packet}
サーバは以下の部分でメッセージを受け取っています。
server() ->
......
loop(Sock).
loop(Sock) ->
receive
{udp, Sock, Sender_IP, Sender_Port, Packet} ->
.......
loop(Sock) %再帰
end.
今回は 再帰関数loop(Sock) を使って 関数server に連続したメッセージを読み取らせています。
クライアントからのメッセージは、上記の送信形式と同じになっているのがわかると思います。
受信メッセージの詳細を見てみましょう。
サーバでは届けられたメッセージから、送信者を特定し、返信を行なっています。
以下の部分です。
receive
{udp, Sock, Sender_IP, Sender_Port, Packet} ->
io:format("S: recv ~p~n",[Packet]),
NewPacket = list_to_binary([<<"recv: ">>,Packet]),
gen_udp:send(Sock, Sender_IP, Sender_Port, NewPacket),
loop(Sock)
end.
サーバからの返信メッセージをクライアントが受け取っています。
コードは次の部分です。
receiver(Sock) ->
receive
{udp, Sock, _IP, _InPortNo, Packet} ->
io:format("C: ~p~n",[Packet]),
receiver(Sock)
after
1000 ->
io:format("good bye~n",[]),
gen_udp:close(Sock)
end.
今回は1秒以上メッセージが届かなかったら、通信が終了したものとしています。
ソケットの切断については次の項目で扱います。
ここまでの実行結果は以下の通りです。
S: recv <<"hello">>
S: recv <<"erlang">>
C: <<"recv: hello">>
C: <<"recv: erlang">>
S: recv <<"java">>
C: <<"recv: java">>
TCPの通信と違い、UDPはソケット同士を接続しません。 したがって、どちらかのソケットを閉じたとしても相手側のソケットには全く影響がありません。
今回は通信を終了したクライアントがソケットを閉じています。サーバのソケットについては解放されたままです。
コードの以下の部分でソケットを閉じています。
gen_udp:close(Sock)
ソケットを指定してポートを閉じるだけです。
関数の形式は以下の通りです。
以上で gen_udp による通信は終了しました。ポートを閉じた際の実行結果は以下の通りです。
good bye
ok