Hoppy を使ってみる

自分、このブログ作らせた言い出しっぺなのに記事全然書けてませんね。
文章書くの好きだけども、苦手だからか。
ずっと書かないのもなんなので、仕事で触れたネタは極力書くようにします。

今、Flash を使ってリアルタイム Web するお仕事をしてます。Flash は余り詳しくないのですが、XMLSocket という機能を使って サーバと通信する事で、それを実現出来るらしいですね。

結構昔からある技術らしくてサーバとしてのソリューションはそれなりの数が出ているらしいけれども、何がいいかはよくわからない、というところで Hoppy を使ってみる事にしました。選んだ理由は、Perl で書かれていたから。とは言え、いわゆるモダン Perl という奴も実は余り詳しくなかったりします。Perl の知識は10年以上前に CGI とか書いてた頃のままなので。とはいえ、Java とかで書かれてる他のソリューションよりは扱い易いかな、という気がしたんです。Java も不案内なので。そうするとお前何が出来るんだよ、とかいう話になりそうですが、そんな事考えると眠くなるので、弄ってみる事にします。

で、使おうと決めたらまずは資料探しなのですが、残念な事に余りヒットしない。

このくらい?

仕方ないので体当たりで使ってみる事にしました。しかし、恐しい事に「POEって何?」というところから始まる手探り感です。まずは Hoppy.pm を眺めてみます。

Hoppy クラスを new した時に _setup メソッドが呼ばれて POE の本体部分が初期化されるのだな。


POE::Component::Server::TCP->new(
	Alias => $self->config->{alias} || 'xmlsocketd',
	Port  => $self->config->{port}  || 10000,
	ClientConnected    => sub { $self->_tcp_handle( Connected    => @_ ) },
	ClientInput        => sub { $self->_tcp_handle( Input        => @_ ) },
	ClientDisconnected => sub { $self->_tcp_handle( Disconnected => @_ ) },
	ClientError        => sub { $self->_tcp_handle( Error        => @_ ) },

	ClientFilter => $filter,
	InlineStates => {
		Send => sub {
			$self->_tcp_handle( Send => @_ );
		},
	},
);
POE::Kernel->sig( INT => sub { POE::Kernel->stop } );

その前に、_load_classes メソッドで諸々の、部品として使われそうな各クラスを読み込んでいる。これは、プラガブルにするための工夫だよね。

で、そうして作った POE を start メソッドで走らせ始める、とそういう流れらしい。その程度のホントに漠然とした理解ですけど、実際に使ってみようと書いたコードが以下

test.pl

#!/usr/bin/perl

BEGIN {
	unshift(@INC, '.');
}

use Hoppy;
use Data::Dumper;

my $config = {
	alias => 'hoppy',
	port => 12345,
	test => 2,
};

my $server = Hoppy->new(config => $config );

$server->regist_service(
	broadcast => 'MyService::Broadcast',
	unicast => 'MyService::Unicast',
	multicast => 'MyService::Multicast',
);

$server->regist_hook(
	client_input => 'MyHook::ClientInput',
);

$server->start;

基本的には、作者様のスライドで示されたサンプルの猿真似。

最初に @INC を弄っているのは、同一パスにある Hoppy を CPAN で入れた物より優先ささせるため。その下で読み込んでる Data::Dumper と組み合わせて、本体のあちこちに中身どうなってるのか変数をダンプしてまわる行を足してみたかったのでこういう事をしてます。

Hoppy.pm 内には、broadcast, multicast, unicast の3つのメソッドがあったのでそれらを全て試してみることに。また、Hook の仕組みも試してみたかったので、クライアントからの入力をフックして処理を走らせるようにもしてみました。

MyService/Broadcast.pm

package MyService::Broadcast;

use base qw(Hoppy::Service::Base);
use Data::Dumper;

sub work {
	my $self = shift;
	my $args = shift;
	my $c = $self->context;
	my $in_data = $args->{in_data};
	my $poe = $args->{poe};
	my $session_id = $poe->session->ID;

	print "now broadcastting\n";

	$c->broadcast({
			sender => $session_id,
			message => $in_data->{params}->{message},
		});

	print "broadcasting done\n";
}

1;

MyService/Multicast.pm

package MyService::Multicast;

use base qw(Hoppy::Service::Base);
use Data::Dumper;

sub work {
	my $self = shift;
	my $args = shift;
	my $c = $self->context;
	my $in_data = $args->{in_data};
	my $poe = $args->{poe};
	my $session_id = $poe->session->ID;
	my $user_id = $in_data->{params}->{user_id};

	print "now multicastting\n";

	$c->multicast({
			sender => $session_id,
			room_id => $c->{room}->{where_in}->{$user_id},
			message => $in_data->{params}->{message},
		});

	print "multicasting done\n";
}

1;

MyService/Unicast.pm

package MyService::Unicast;

use base qw(Hoppy::Service::Base);
use Data::Dumper;

sub work {
	my $self = shift;
	my $args = shift;
	my $c = $self->context;
	my $in_data = $args->{in_data};
	my $poe = $args->{poe};
	my $session_id = $poe->session->ID;
	my $user_id = $in_data->{params}->{user_id};

	print "now unicastting\n";

	$c->unicast({
			sender => $session_id,
			user_id => $in_data->{params}->{target_id},
			message => $in_data->{params}->{message},
		});

	print "unicasting done\n";
}

1;

MyHook/ClientInput.pm

package MyHook::ClientInput;

use Data::Dumper;
use base qw(Hoppy::Hook::Base);

sub work {
	my $self = shift;
	my $args = shift;
	my $c = $self->context;

	print Dumper($c);
}

1;

これで、チャットサーバ的な物が出来ました。

test.pl を実行しておいて、telnet から接続し、以下のような入力を順に送るとメッセージが送れたりします。(送った本人には届かないので、テストの為には二カ所以上から telnet する必要があります)

{"method":"login","params":{"user_id":"test","room_id":"testing_room"}}
{"method":"broadcast","params":{"user_id":"test","message":"broadtestes"}}
{"method":"multicast","params":{"user_id":"test","message":"multitestes"}}
{"method":"unicast","params":{"user_id":"test","target_id":"test2","message":"unitestes"}}

Perl よくわからないながらも、とりあえず動くようになったので、もう少しいじり回してみようと思います

preload preload preload