ノンブロッキングWebアプリケーションを構築する例

最近、Webでの非ブロッキングソケットとイベント指向プログラミングの使用を説明する2つの habratopik( 1、2 )を見ました。 このテクノロジーでWebアプリケーションを作成した経験を共有したいと思います。



最近、私は自分のICQ番号不可視性チェックサービスを作りたかったです。 検証アルゴリズムは古く、よく知られていますが、まだ機能しています-特別に細工されたサービスメッセージを送信し、サーバーの応答を分析します。 ICQサーバーへの永続的な接続をいくつか維持し、検証要求用のWebインターフェイスが必要でした。 明らかな解決策は、ICQ接続用の複数のスレッドを作成するデーモンを作成し、複数のワーカープロセス(またはプリフォークされたアーキテクチャ)を使用するWebアプリケーションからコマンドを受け取り、複数のクライアントからのhttp要求を処理できるようにすることです。 しかし、私は自分で新しい技術を学び、複数の接続をサポートし、1つのストリームのみを使用して顧客に応答するアプリケーションを作成することにしました。



イベントループ



Perlには、イベント指向アプリケーションを作成するためのフレームワークであるイベントループの多くの実装があります。 これらは、さまざまなイベントハンドラー(タイマー操作、信号の受信、ソケットで読み取れるデータの外観)を登録するためのインターフェイスと、プログラムの実行がブロックされてイベント処理が開始される関数を提供するモジュールです。 イベントが発生すると、登録時に指定されたコールバックが呼び出されます。 多くのイベントループには、高性能kqueueやepollなどのイベントアラートのバックエンドを指定する機能があります。

1つまたは別のイベントループを使用するCPANには多くのモジュールがあります。 問題を解決するために必要なすべてのモジュールが存在する場合がありますが、それらは互換性のないインターフェイスで異なるイベントループを使用します。 そのような場合はどうすればいいですか?



Anyvent



AnyEvent-イベント指向プログラミングのDBI。 AnyEventは、使用されているイベントループをいつでも別のものに変更できるインターフェイスを提供します。 また、AnyEventを使用すると、異なるイベントループを使用するモジュールを一緒に使用できます。 それが、私がAnyEventを使用してICQプロトコルの実装を書いた理由です。



ICQプロトコル



主なタスクは、ICQメッセージサーバーに接続し、検証アルゴリズムを実装することです。 CPANで利用可能な3つのモジュールは非常に古く、ブロッキングソケットで実行されていたため、私には不向きでした。 結局、コールバック内のロックは他のすべてのイベントの処理をブロックします! しかし、それでも、簡単にするために、他の言語とこれらのモジュールで多数の既製のソリューションを使用して、最初にブロッキングソケットの実装を行い、次にAnyEventですべてを作り直し始めました。 以下は、1つのICQパケット-FLAPを受信するためのコードです。



# <br/>

sub recv { <br/>

my ( $self ) = @_ ; <br/>

<br/>

# 6 - FLAP <br/>

sysread $self -> socket , my $data , 6 ; <br/>

# <br/>

my $length = unpack ( 'n' , substr ( $data , - 2 ) ) ; <br/>

# - <br/>

my $channel = unpack ( 'C' , substr ( $data , 1 , 1 ) ) ; <br/>

# , <br/>

sysread $self -> socket , $data , $length ; <br/>

# OC::ICQ::FLAP <br/>

return new OC :: ICQ :: FLAP ( $channel , $data ) ; <br/>

} <br/>

<br/>

# <br/>

sub connect { <br/>

my ( $self , $host , $port ) = @_ ; <br/>

<br/>

# AnyEvent::Handle <br/>

$self -> { io } = new AnyEvent :: Handle ( <br/>

connect => [ $host , $port ] , <br/>

# <br/>

on_error => sub { $self -> on_error ( @_ ) } , <br/>

on_disconnect => sub { $self -> on_disconnect ( @_ ) } , <br/>

) ; <br/>

<br/>

# , , 6 - FLAP <br/>

$self -> { io } -> push_read ( chunk => 6 , sub { $self -> on_read_header ( @_ ) } ) ; <br/>

} <br/>

<br/>

# , FLAP <br/>

sub on_read_header { <br/>

my ( $self , $io , $header ) = @_ ; <br/>

<br/>

# - OC::ICQ::FLAP <br/>

$self -> { flap_channel } = unpack ( 'C' , substr ( $header , 1 , 1 ) ) ; <br/>

# , <br/>

$io -> push_read ( chunk => unpack ( 'n' , substr ( $header , - 2 ) ) , sub { $self -> on_read_data ( @_ ) } ) ; <br/>

} <br/>

<br/>

# , <br/>

sub on_read_data { <br/>

my ( $self , $io , $data ) = @_ ; <br/>

<br/>

# , OC::ICQ::FLAP, <br/>

$self -> _process_flap ( new OC :: ICQ :: FLAP ( $self -> { flap_channel } , $data ) ) ; <br/>

# FLAP <br/>

$io -> push_read ( chunk => 6 , sub { $self -> on_read_header ( @_ ) } ) ; <br/>

}








_process_flap



にあるロジックのほとんどすべてをやり直す必要がありました。 接続を維持するには、2分ごとに空のFLAPをチャネル5で送信する必要があります。 これを行うには、AnyEventが提供するtimer



機能を使用できます。



# , 2 <br/>

$self -> { keepalive_timer } = AnyEvent -> timer ( after => 120 , interval => 120 , cb => sub { $self -> send_keepalive } ) ; <br/>

<br/>

sub send_keepalive { <br/>

my ( $self ) = @_ ; <br/>

<br/>

if ( $self -> { state } == ONLINE ) { <br/>

$self -> send ( new OC :: ICQ :: FLAP ( 5 , '' ) ) ; <br/>

} <br/>

} <br/>

<br/>

# . $self->{keepalive_timer} <br/>

delete $self -> { keepalive_timer } ;








Webインターフェース



さて、ICQプロトコルと検証アルゴリズムの実装の準備が整いました。今ではWebインターフェイスが必要です。 WebアプリケーションをWebサーバーに接続するための素晴らしいFastCGIプロトコルがあり、CPANでEVIO :: Asyncの非同期実装を2つ見つけました。 私はそのパフォーマンスのためにEVを選びました 。 次に、単純な属性ベースのURLディスパッチャーが作成され、単純なText :: MicroMasonテンプレートエンジンがねじ込まれました。すべて、非同期Webアプリケーションを作成するためのミニフレームワークの準備ができました。

テキスト:: MicroMasonはテンプレートをメモリにコンパイルされた形式で保存します。これはパフォーマンスに大きな影響がありますが、テンプレートを変更する必要がある場合はどうなりますか? すべてのICQクライアントの接続を切断して、デーモンを停止しないでください? AnyEventとEVは、シグナルにハンドラーを設定する機能を提供します。これは使用できます。

my $sigusr1_watcher = EV :: signal ( 'USR1' , \&restart ) unless $^O =~ /MSWin32/ ; <br/>

my $sigusr2_watcher = EV :: signal ( 'USR2' , \&load_templates ) unless $^O =~ /MSWin32/ ; <br/>







これで、SIGUSR1を受信すると、構成が再ロードされ、古いICQクライアントがすべて削除されて新しいクライアントが作成されます。SIGUSR2を受信すると、テンプレートが再ロードされます。 タイマーの場合と同様に、戻り値EV :: signal / AnyEvent-> signalを必ず保持してください。



All Articles