2009-07-05

[perl] Danga::Socket::Callback を使ったキャラクタ echo サーバー

Perl で epoll, kqueue などを使った、イベントドリブンで効率的なネットワークアプリケーションを作成できる Danga::Socket を、手軽に扱えるようにした、Danga::Socket::Callback について、日本語の情報が少ないので書いてみました。

TokuLog 改めB日記 の tokuhirom さんの書かれた、 Danga::Socket::Callback がクールな件について
この記事のコードでは、on_write_ready コールバックが、それが不必要になったあと (書きこみバッファが空になった後) も延々と呼び出されてしまい、CPU使用率が 100% にはりつき、無駄に資源を消費するので、地球環境に良くないコードです。

そもそも、on_write_ready などの、書きこみバッファの減少をとらえる仕組みは、普通のアプリケーションには不要だと考えます。

2012/11/25 追記
この説明は不適切で、読み取り側ストリームが書き込み側ストリームが許容する書き込み速度よりゆっくりな場合のみ、書き込みバッファのことを気にすることなく write できるという条件付きの話でした。

例えば上りが速く下りが遅いクライアントがエコーサーバーに上りが飽和するほど入力すると書き込みバッファがあふれる状況も十分考えられます。

use strict;
use warnings;
use Danga::Socket::Callback;
use IO::Socket::INET;

my $LISTENING_PORT = shift || 17000;

my $sock_server = IO::Socket::INET->new(
    Proto     => 'tcp',
    LocalPort => $LISTENING_PORT,
    Listen    => 16,
    ReuseAddr => 1,
) or die "Cannot bind $LISTENING_PORT: $@\n";

print "Start listening on port: $LISTENING_PORT\n";

$sock_server->blocking(0);

Danga::Socket::Callback->new(
    handle => $sock_server,
    on_read_ready => sub {
        my $dsock_server = shift;
        my $sock_client = $dsock_server->sock()->accept() or return;
        $sock_client->blocking(0);

        Danga::Socket::Callback->new(
            handle => $sock_client,
            on_read_ready => sub {
                my $dsock_client = shift;

                my $buf = $dsock_client->read(4096);

                unless ($buf) {
                    print "close\n";
                    $dsock_client->close();
                    return;
                }

                my $str = $$buf;
                $str =~ s/(\W)/'%' . unpack('H2', $1)/ge;
                print "recv: $str\n";

                $dsock_client->write($buf);
            },
        );
    },
);

Danga::Socket->EventLoop();

ポイント
待ち受け用のソケットは最初から Non-Blocking モードで始めるのではなく、Blockingモードで始めたあと、Non-Blocking モードに移行した方が接続時のエラー検出が簡単にできる。

Danga::Socketの->read() が返してくれるものは、文字列の参照なので、その点注意する。