2009-12-31

[perl] AnyEvent のファイルハンドル操作

ソケットを扱うとき


TCP/IP のソケットや、Unix ドメインソケットを扱いたいときは、低水準で高速な AnyEvent の IO watcher または、もうちょっと高級な、AnyEvent::Handle を使う。

ローカルファイルを扱うとき

AnyEvent::AIO + IO::AIO を使う。

ローカルファイル (ブロックデバイス) に対しては、AE::io は使えないと AnyEvent の I/O WATCHERSの項 に書いてあります。

2009-12-29

PerlのEncodeモジュールは何を何にエンコードするのか

概要

Perlの文字コード変換用のモジュールEncode.pmの入門用記事は色々ありますが、入門の入門が必要なくらい説明が足りていないと思うので、記事にしてみます。

タイトルの問題の答え

タイトルにした問題の答えは、「夢の国のPerl内部表現文字列を、現実の世界の文字列(バイト列)にエンコードする(変換する)」です。

具体的には、関数$bytes = encode($charset, $string)はPerl内部表現文字列から外に出ていくバイト列を生成する(エンコードする)のに使って、$string = decode($charset, $bytes)は外からバイト列をPerl内部表現にデコードするという感じです。

ご存知かとは思いますが、モジュールの名前はEncodeなのに、なんと逆変換のdecodeもできます。

混乱を誘うさまざまな機能

そして混乱することに、utf8encodingがあります。これらのモジュールはすべて小文字なので、Perlインタプリタの動作を変更するプラグマモジュールであり、ソースコードのエンコーディングを指定するという意味です。

このプラグマを有効にすると、文字列リテラルが単なるバイト列ではなく、Perl内部表現にdecodeされた状態で構文木が作られます。そんな必要がないなら指定してはいけません。

またさらに混乱することに、PerlIOの拡張によって、標準入出力、エラー出力、ファイル入出力で指定可能になる、フィルタの機能を使うと、透過的にエンコード、デコードが起こって何が起こっているのか良く分からなくなるので、encode, decodeを自在に操れるようになってから触るとよいです。

デバッグ方法

Devel::PeekでPerl内部表現文字列かどうかを表すutf8フラグを含めてDumpして観察する。\012みたいなのは1バイトを8進数3桁で表したもの。

2009-12-24

[perl] 今日から始める AnyEvent

AnyEvent とは

これまで、イベントドリブンフレームワークの実装には、POE, Danga::Socket, EV, Event などいろいろあって、互換性もなく混乱した状況でした。

AnyEvent はそういう諸々の API を、データベースへの API で考えられた DBI の様に抽象化したもので、具体的な実装には依存せず、どの(Any)イベントドリブンフレームワークを用いても、同じコードで動きます。今後は AnyEvent で書いておけば安心です。

基本的な考え方

  1. イベントハンドラにコールバック関数を登録します。
    具体的には、on_readみたいなところに関数リファレンスを入れておく。
  2. イベントループを回します。
    具体的には、$cv = AE::cv でcondvarを作って、$cv->recv で処理をブロックしておけばよい。

AnyEvent->condvar がピンとこない

Condition variable? 状態は送信前と送信後の2つあります。ボンバーマンで考えると、リモコンボムです。具体的には、爆発する前の状態と、リモコンを使って爆発させた後の状態の2つです。

送信側は、$cv->send
受信側は、$cv->recv (届くまでブロック) または、$cv->cb($cb) (届いたら$cb->($cv)が実行される)

AE と AnyEvent の違いが良くわからん

AE の方が改良版の高速なAPIです。

AnyEvent->condvar と AE::cv
$w = AnyEvent->timer(after => $after, interval => $interval, cb => $cb) と $w = AE::timer($after, $interval, $cb)
以上の関係を覚えればだいたいOK

hello world

こんな感じでいかがでしょうか。helloが出て、1秒後に world が出ます。

use strict;
use warnings;
use AnyEvent;

my $done = AE::cv;
my $t = AE::timer 1, 0, sub {
    print "world\n";
    $done->send;
};

print "hello\n";

$done->recv;

あとは公式ドキュメントを読む

まずは、AnyEvent::Intro
詳細は、AnyEvent

2009-12-23

りんごジュースと浸透圧について

100%アップルジュースをそのまま飲むとちょっとくどい感じがするので、2倍くらいに薄めて飲むのがちょうどいいと感じていましたが、その根拠についてなんとか計算できたのでメモ。

アップルジュースのパッケージの成分表示を見ると、炭水化物 12.5g / 100mlと書いてあって、りんごに含まれる糖分は果糖やブドウ糖の単糖類。単糖類の分子量は180というのを使うと、100%アップルジュースの糖分のモル濃度は、0.69 mol/L

ところで、生理食塩水(0.9%)の水和性分子のモル濃度を考えると、NaClの式量が58.5というのを使って、完全に電離してイオン2つ分になるとして考えると、0.31 mol/L

よって、アップルジュースも半分位に薄めたらいい感じの浸透圧になりそうな事がわかります。

2009-12-21

moo cards

細長い名刺はmoo MiniCardsで、イギリスからの空便でした。

アドレスは、http://uk.moo.com/en/products/minicards.php クーポンコードを探して入れたらもう少し安くなります。

2009-12-05

[perl] DateTime による日時処理についてのメモ

とても便利なのにまだ十分に活用されていない気がする、DateTime モジュールについて、実際に使ってみて気づいたことを書いておきます。

W3CDTF

DateTime::Format::W3CDTF は小数部を含む秒表現に対応していないので、W3CDTF のスーパーセットである ISO8601形式 に対応した DateTime::Format::ISO8601 を使う。

RSS

RSS の日時表現をパースするときは、表現が何種類かあるので、曖昧さに対応している、DateTime::Format::RSS を使う。

ミリ秒

時間精度が欲しいログを取る際などに、ミリ秒を記録したいときは、DateTime::HiRes を使って、

DateTime::HiRes->now->strftime('%Y-%m-%d %H:%M:%S.%3N')

という感じで出力する。%3N がミリ秒の部分となる。

JavaScript などで欲しくなる、ミリ秒でのUNIX epochが必要なときは、

DateTime::HiRes->now->strftime('%s%3N')

とする。

デバッグ

変な感じのときは、Data::Dumper で、内部変数を見てみる。

2009-10-29

実践的な暗証番号(PIN)の作り方

今後ICカードで個人の秘密鍵を保存するような時代が来れば、より重要になると考えられる暗証番号の作り方について、いまいち実践的なガイドがないので記事を書いてみました。

以下の意味を持っている数値:

  • (強度:中) 好きな事件の年号、好きな山の標高、好きな企業の電話番号、好きな機械の型番、好きなICの品番

  • (強度:弱) 学生時代の学籍番号の一部、免許証番号の一部、クレジットカード番号の一部、口座番号の一部、下4けたをそのまま使うのは避ける。

  • (強度:弱) 連番(1234,8901,6543,1111)

  • (強度:中強) ひらがなを暗号化:ベル打ちで暗号化、子音のみを取り出してアカサタナ...=12345...で置き換える。

  • (強度:中強) アルファベット4文字をアメリカの電話番号方式で数字に置き換える

以上の中から任意の2つを選び、適当な仕組み(頭の中で考えられる程度)で組み合わせて、数値そのものの意味を薄めた数値を暗証番号として採用する。

組み合わせ方の例:

  • AAAB
  • AABB
  • ABBA
  • ABAB

実際の例:
組:4321と0794(平安京遷都)、組み合わせ法:ABBA、結果:4791

最後に、身近な数値に一致していないかをテストする。例:誕生日、車のナンバー、番地、家の電話番号の下4桁

完全にランダム生成しても、2週間くらい毎日思い出せば覚えられるので、若い人はそれでも可。

2つの数値と、組み合わせ法すら忘れてしまう人には使えません。

実際にこの方法を採用する場合でも、安全性は保障できません。自己責任にてどうぞ。

2009-07-17

[perl] 標準出力と標準エラー出力をファイルに記録しつつ出力する

必要になったので調べていたのですが、himazu blog さんの、STDOUTとSTDERRをファイルにも出力するようにする では、解決していないようなので自分で考えてみました。

これにより、本文中の print 文には一切触れずに、STDOUT (標準出力) と STDERR (標準エラー出力) をこっそりロギングする仕組みを追加でき、STDERR に出る warning も捕捉できるので、とても便利だと思います。

この機能の実現に必要な、ファイルハンドルを多重化するしくみは、CPAN モジュールで、IO::TeeFile::Tee が見つかったのですが、どうやら、File::Tee が簡単に使えそうです。

ひとつ残念なところは、File::Tee は Windows に対応していない点です。

use File::Tee qw(tee);

open(my $log_fh, '>>', 'output.log');
tee(STDOUT, $log_fh);
tee(STDERR, $log_fh);

print STDERR "stderr\n";
print "stdout\n";
warn "warning";
system("echo system stdout");
system("echo system stderr >&2");

せっかく前処理部分を書けるので、無駄にタイムスタンプ機能などを追加してみました。

use DateTime;
use DateTime::TimeZone;
use File::Tee qw(tee);

my $tzlocal = DateTime::TimeZone->new(name => 'local');

sub get_prepender {
    my $label = shift;

    return sub {
        my $data = shift;
        return join(' ',
            DateTime->now()->set_time_zone($tzlocal)->datetime(),
            $label, $data
        );
    };
}

open(my $log_fh, '>>', 'output.log');
tee(STDOUT, {open => $log_fh, preprocess => get_prepender('STDOUT')});
tee(STDERR, {open => $log_fh, preprocess => get_prepender('STDERR')});

print "stdout\n";
print STDERR "stderr\n";
warn "warning";
system("echo system stdout");
system("echo system stderr >&2");

記録例 (output.log)

2009-07-17T00:12:53 STDOUT stdout
2009-07-17T00:12:53 STDOUT system stdout
2009-07-17T00:12:53 STDERR stderr
2009-07-17T00:12:53 STDERR warning at redirect.pl line 29.
2009-07-17T00:12:53 STDERR system stderr

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() が返してくれるものは、文字列の参照なので、その点注意する。

2009-06-26

twitter live streaming を公開


twitter live streaming

説明
Twitter の Streaming API を用いて、twitter live streaming を作ってみました。 Twitter の最新投稿の一部がブラウザまで、ほぼ最速で届きます。

詳細
Twitter の Public Timeline のごく一部分をサンプリングして配信される: https://stream.twitter.com/1/statuses/sample.json からの情報を、タイムゾーンが東京のもののみを抽出してから、配信しています。

具体的には、Heroku にて、Node.jsで作ったServer-Sent Eventsプロトコルで流してくれるサーバを用意し、クロスドメインに対応したライブラリ Yaffle/EventSource で受信しています。

Twitterへの接続にはクライアントライブラリ ntwitter を使っています。

2009-02-07

はじめまして

せっかくドメインをとったので、ブログを開設してみました。

よろしくお願いします。