Erlang World


top > distributed programming > gen_udp

gen_udp

UDPによるソケット通信の概観

UDPによるソケット間通信はTCPのものよりも単純です。
それは

  1. サーバがポートを開ける
  2. クライアントがポートを開ける
  3. クライアントが開けたポートより、サーバのポートに向けてメッセージを送信
  4. サーバはポートからメッセージを届けられる。

というものです。

お気づきの方が多いと思いますが、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

UDP通信を使用するポートを開く

TCPによる通信と違いUDPにおいてサーバとクライアントは接続されません。
サーバの所定の場所にメッセージをクライアントが送るという形です。

server() の以下の部分が、サーバのポートを開く部分です。

{ok, Sock} = gen_udp:open(?SERV_PORT, [binary]),
open(Port) -> {ok, Socket} | {error, Reason}
open(Port, Options) -> {ok, Socket} | {error, Reason}
Port = 0..65535
Options = [Opt]
Socket = socket()
指定できるオプションはgen_tcpと同じです。
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(Socket, Address, Port, Packet) -> ok | {error, Reason}
Socket = socket()
Address = string() | atom() | ip_address()
Port = 0..65535
Packet = [char()] | binary()
Reason = not_owner | posix()
ポートを開く際に作成したソケットSocketより、アドレスAddressにあるポートPortに向かって メッセージPacketを送信します。

関数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 に連続したメッセージを読み取らせています。 クライアントからのメッセージは、上記の送信形式と同じになっているのがわかると思います。
受信メッセージの詳細を見てみましょう。

{udp, Socket, IP, InPortNo, Packet}
Socket: メッセージを受け取った自分のソケット
IP: 送信者のIPアドレス
InPortNo: 送信者が使用したポート番号
Packet: メッセージ本体

サーバでは届けられたメッセージから、送信者を特定し、返信を行なっています。
以下の部分です。

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)

ソケットを指定してポートを閉じるだけです。
関数の形式は以下の通りです。

close(Socket) -> ok | {error, Reason}
Socket = socket()
Reason = not_owner | posix()
UDPのソケットを閉じます。

以上で gen_udp による通信は終了しました。ポートを閉じた際の実行結果は以下の通りです。

good bye
ok

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