amari3のはてなダイアリー このページをアンテナに追加 RSSフィード

2010-04-04

[][]AnyEvent で echo サーバ作ってみた

最近ちょくちょく勉強している、AnyEvent で簡単な echo サーバを作ってみた。

入力された文字を単純に返すだけだと面白くないので、入力した時の日時を表示するようにしてみた。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use DateTime;
use AnyEvent;
use AnyEvent::Socket;

my $HOST = '127.0.0.1';
my $PORT = '18888';

tcp_server $HOST, $PORT, sub {
    my ($fh, $host, $port) = @_;
    say "connected $host:$port";

    &add($fh);
};

AnyEvent->condvar->recv;

sub add {
    my $fh = shift;
    my $watcher;
    $watcher = AnyEvent->io(
        fh   => $fh,
        poll => 'r',
        cb   => sub {
            my $length = sysread $fh, my $buf, 256;
            if (! $length) {
                undef $watcher;
                return;
            }
            my $dt = DateTime->now(time_zone => 'Asia/Tokyo');
            syswrite $fh,
                '[' . $dt->ymd('/') . ' ' . $dt->hms . "] $buf";
        }
    );
}

echo サーバを起動する。

% perl ae-echo-server.pl

echo サーバに接続して、利用してみる。

% telnet 127.0.0.1 18888
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
amari3
[2010/04/05 00:35:53] amari3
AnyEvent
[2010/04/05 00:35:58] AnyEvent
hoge
[2010/04/05 00:36:01] hoge
foo
[2010/04/05 00:36:04] foo
baz
[2010/04/05 00:36:05] baz
echo server
[2010/04/05 00:36:15] echo server
^]

簡単な echo サーバだったらこんなに短いソースコードで実現できてしまう。もっと勉強してなんか面白いものを作ってみたいですね。

2010-03-29

[][]AnyEvent でイベント駆動プログラミング

昨年の YAPC で非同期系のセッションが人気があったので、ちょっと遅いけど、手を出してみることにする。

AnyEvent を小2時間ほどいじってみたので、自分なりの解釈で記事を書くことにする。

イベント駆動プログラミングって何?

プログラムが上から下に実行される、いわゆる手続き型プログラミングとは違い、イベントハンドラにコールバック関数を登録しておき、イベントループを回します。イベント発生をトリガとして、イベントハンドラに登録されているコールバック関数を実行するプログラミング手法。

ちなみにイベントとは以下のようなことを指します。

  • キーボード押された
  • マウスがクリックされた

Windows プログラミングの経験者であれば、すんなり入っていくことができると思います。GetMessage のイベントループとか WindowProc とか例のやつです。僕も、Windows プログラミングの経験者だったので、すんなり入ることができました。

初めての AnyEvent

1秒毎に現在時刻を表示し、標準入力(STDIN)から「quit」が入力されるまで、監視をするプログラムのサンプルです。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use AnyEvent;

# 1秒ごとに現在時刻を表示するイベント
my $cvt = AnyEvent->condvar;
my $t;
   $t = AnyEvent->timer(
    after    => 0,
    interval => 1,
    cb => sub {
        say AnyEvent->time;
        $cvt->send;
    },
);
$cvt->recv;

local $| = 1;

# 標準入力から入力イベント
# quit が入力されるとイベントループを抜ける
IO:
for (;;) {
    my $cv = AnyEvent->condvar;
    my $wait_for_input;
       $wait_for_input = AnyEvent->io(
        fh   => \*STDIN,
        poll => 'rl',
        cb   => sub {
            chomp(my $input = <STDIN>);
            undef $wait_for_input;
            $cv->send($input);
        }
    );
    my $input = $cv->recv;
    say "[$input]";
    last IO if defined $input && $input eq "quit";
}

undef $t;
say "done.";

ソースコードの前半では、タイマーのイベントを作成しています。interval で指定した秒数毎に登録してあるコールバック関数が呼び出される仕組みになっています。

ソースコードの後半では、標準入力を監視するイベントを作成しています。標準入力から入力があったときに、登録してあるコールバック関数が呼ばれる仕組みになっています。

ソースコード内に度々でてくる、AnyEvent->condvar は、状態変数と呼ばれるもので、メインループに対してイベントの状態を通知するための変数です。

$cv->send で状態変数を変更し、$cv->recv は状態変数が変更されるまで、つまり、$cv->send が呼び出されるまでコードをブロックしています。明快な説明じゃないですね。。ちゃんと説明できるようになったら修正しよう。

ちなみに実行してみるとこんな感じになります。

% perl ae3.pl
1269878022.06494
1269878023.06862
1269878024.0677
amar1269878025.06774
i3
[amari3]
1269878026.06878
1269878027.06878
quit
[quit]
done.

勉強を始めたばっかりで、説明に誤りがあるかもしれないけど、おもしろいので引き続き勉強していきます。