UDP を使ってみよう (2)

前へ << UDP を使ってみよう (1) UDP を使ってみよう (3) >> 次へ

UDP ソケット操作

UDP プロトコルは単純ですから、ソケット操作自体も単純です。 まずはサーバ側の書き方から。
socket(SOCKET, PF_INET, SOCK_DGRAM, 0);
bind(SOCKET, pack_sockaddr_in(7000, INADDR_ANY));
recv(SOCKET, $buf, 10000, 0);
  1. まず socket でソケットを生成します。 TCP では SOCK_STREAM でしたが、UDP では SOCK_DGRAM とします。
  2. 任意のポートへ bind します (この例ではポート 7000 を使っています)。ここは TCP と全く同じです。
  3. データの取得は recv を使います。上記の書き方だと 10,000 バイトの データをソケットから読み取り、$buf に格納します。 recv の最後の引数はフラグで、ここでは無指定 (0) です。
TCP と比べてみましょう。

UDP TCP 意味
socket (SOCK_DGRAM) socket (SOCK_STREAM) ソケット生成
bind bind ソケットとポートと結合
- listen ポート受け付け開始
- accept コネクションを取得
recv read (<SOCKET> で1行読み込み) データ読み込み


一方クライアント側は以下のような手順になります。

socket(SOCKET, PF_INET, SOCK_DGRAM, 0);
$iaddr = inet_aton('targethost');
$sock_addr = pack_sockaddr_in(7000, $iaddr);
send(SOCKET, "hoge", 0, $sock_addr);
  1. socket でソケットを生成します。 ここでも SOCK_DGRAM として UDP を指定します。
  2. inet_aton と pack_sockaddr_in で、ソケットアドレス構造体を作ります。 この例では、targethost のポート 7000 になります。
  3. データ "hoge" を targethost:7000 に送ります。 send の第3引数はフラグで、ここでは無指定 (0) です。
こちらも TCP と比べてみます。

UDP TCP 意味
socket (SOCK_DGRAM) socket (SOCK_STREAM) ソケット生成
inet_aton
pack_sockaddr_in
inet_aton
pack_sockaddr_in
ソケットアドレス構造体を作成
- connect サーバに接続
send write (print SOCKET "...") データ送信

UDP クライアント & サーバ

今回作成したプログラムは、クライアントとサーバがペアになっています。 まずサーバを
% ./udp-server-1.pl
として起動します。すると UDP のポート 7000 に届いたデータを表示します。

次に (サーバ側を実行させたまま) 同じマシン上で

% ./udp-client-1.pl localhost 7000
としてクライアントを起動します。

クライアントが起動すると、引数で指定した localhost:7000 に対して、

Hello 1
Hello 2
Hello 3
Hello 4
  :
Hello 99998
Hello 99999
Hello 100000
と、「Hello 送信回数」というデータを 10万回サーバに送信します。 サーバは
受信データ=Hello 1 (受信回数 1)
受信データ=Hello 2 (受信回数 2)
受信データ=Hello 3 (受信回数 3)
受信データ=Hello 4 (受信回数 4)
   :
と受信したデータを表示しはじめます。普通に考えると、このまま 100,000 個のデータを受信し、最後は
   :
受信データ=Hello 99999 (受信回数 99999)
受信データ=Hello 100000 (受信回数 100000)
となるように思えるのですが、UDP ですから そうはなりません。 FreeBSD 4.4-RELEASE で、クライアントとサーバを同じマシン上で実行した結果、 以下のようになりました。
   :
受信データ=Hello 512 (受信回数 512)
受信データ=Hello 513 (受信回数 513)
受信データ=Hello 1624 (受信回数 514)
受信データ=Hello 1625 (受信回数 515)
   :
受信データ=Hello 98533 (受信回数 3616)
受信データ=Hello 98534 (受信回数 3617)
受信データ=Hello 98535 (受信回数 3618)
513 回目までは正しく送信できているのですが、 514〜1623 まではデータがサーバに届いていません。 その後も、89449〜97804、97871〜98469、98536〜100000 は紛失し、結局届いたのは 100,000 分の 3,618。 到達率 3.6%、つまり
データ損失率 96.4%
というものすごいことになっています。

udp-client-1.pl

    1: #!/usr/local/bin/perl -w
    2: 
    3: # $Id: udp-client-1.pl,v 1.1 2002/02/17 10:19:18 68user Exp $
    4: 
    5: use Socket;     # Socket モジュールを使う
    6: 
    7: use Errno;      # Errno 定数モジュールを使う
    8: 
    9: if ( @ARGV != 2 ){
   10:     print "引数で [接続先ホスト名] [接続先ポート番号] を指定して下さい。\n";
   11:     exit;
   12: }
   13: 
   14:                 # 接続先ホスト名を取得
   15: $host = shift @ARGV;
   16: 
   17:                 # 接続先ポート番号を取得
   18: $port = shift @ARGV;
   19: 
   20:                 # ホスト名を、IP アドレスの構造体に変換
   21: $iaddr = inet_aton($host)
   22:         or die "$host は存在しないホストです。\n";
   23: 
   24:                 # ポート番号と IP アドレスを構造体に変換
   25: $sock_addr = pack_sockaddr_in($port, $iaddr);
   26: 
   27:                 # ソケット生成
   28: socket(SOCKET, PF_INET, SOCK_DGRAM, 0)
   29:         or die "ソケットを生成できません。\n";
   30: 
   31:                 # send する回数
   32: $num_of_senddata = 100000;
   33: 
   34:                 # ENOBUFS が発生した回数
   35: $num_of_enobufs  = 0;
   36: 
   37: for ( $i=1 ; $i<=$num_of_senddata ; ){
   38:     if ( ! send(SOCKET, "Hello $i", 0, $sock_addr) ){
   39:         # エラーが発生した
   40: 
   41:         if ( $! == Errno::ENOBUFS ){
   42:             # 送信バッファがいっぱい (ENOBUFS) ならリトライ
   43: 
   44:             $num_of_enobufs++;
   45:             next;
   46: 
   47:         } else {
   48:             # ENOBUFS 以外なら終了
   49:             die "send に失敗しました ($i)。$!\n";
   50:         }
   51:     }
   52:     $i++;
   53: }
   54: 
   55: print "$host:$port に対して send を $num_of_senddata 回実行しました。\n";
   56: print "ENOBUFS の発生回数 $num_of_enobufs\n";

udp-server-1.pl

    1: #!/usr/local/bin/perl -w
    2: 
    3: # $Id: udp-server-1.pl,v 1.1 2002/02/17 10:19:18 68user Exp $
    4: 
    5: use Socket;     # Socket モジュールを使う
    6: 
    7: $port = 7000;   # ポート番号は 7000 番
    8: 
    9:                 # ソケット生成
   10: socket(SOCKET, PF_INET, SOCK_DGRAM, 0)
   11:         or die "ソケットを生成できません。\n";
   12: 
   13: bind(SOCKET, pack_sockaddr_in($port, INADDR_ANY));
   14: 
   15: $recv_count = 0;
   16: 
   17: while (1){
   18:     recv(SOCKET, $buf, 10000, 0);
   19:     $recv_count++;
   20:     print "受信データ=$buf (受信回数 $recv_count)\n";
   21: 
   22:     # スピード調整のためのウェイト
   23:     # select(undef, undef, undef, 0.01);
   24: }

TCP だとどうなる?

TCP で似たような動作をするプログラムを書いてみました。 これを実行すると、送信したデータは 100% 届くことがわかります。

tcp-client-vs-udp.pl

    1: #!/usr/local/bin/perl -w
    2: 
    3: # $Id: tcp-client-vs-udp.pl,v 1.1 2002/02/17 10:19:18 68user Exp $
    4: 
    5: use Socket;
    6: 
    7: if ( @ARGV != 2 ){
    8:     print "引数で [接続先ホスト名] [接続先ポート番号] を指定して下さい。\n";
    9:     exit;
   10: }
   11: 
   12: $host = shift @ARGV;
   13: $port = shift @ARGV;
   14: 
   15: $iaddr = inet_aton($host) || die "$!";
   16: $sock_addr = pack_sockaddr_in($port, $iaddr) || die "$!";
   17: socket(SOCKET, PF_INET, SOCK_STREAM, 0) || die "$!";
   18: connect(SOCKET, $sock_addr) || die "$!";
   19: 
   20: $num_of_senddata = 100000;
   21: 
   22: for ( $i=1 ; $i<=$num_of_senddata ; $i++ ){
   23:     print SOCKET "Hello $i\n" || die "$!";
   24: }
   25: 
   26: print "$host:$port に対して、データを $num_of_senddata 回送信しました。\n";

tcp-server-vs-udp.pl

    1: #!/usr/local/bin/perl -w
    2: 
    3: # $Id: tcp-server-vs-udp.pl,v 1.1 2002/02/17 10:19:18 68user Exp $
    4: 
    5: use Socket;
    6: 
    7: $port = 7000;
    8: 
    9: socket(SOCKET, PF_INET, SOCK_STREAM, 0) or die "$!";
   10: setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR, 1)  || die "$!";
   11: bind(SOCKET, pack_sockaddr_in($port, INADDR_ANY)) || die "$!";
   12: listen(SOCKET, SOMAXCONN) || die "$!";
   13: accept(NEWSOCK, SOCKET) || die "$!";
   14: 
   15: while (<NEWSOCK>){
   16:     chomp;
   17:     $recv_count++;
   18:     print "受信データ=$_ (受信回数 $recv_count)\n";
   19: }
前へ << UDP を使ってみよう (1) UDP を使ってみよう (3) >> 次へ

ご意見・ご指摘は Twitter: @68user までお願いします。