2006年11月07日

なんとかhttp切断とレスポンス取得に成功

とりあえず安定するFlash用サーバーの雛型ができた。
現段階での固有データはポート番号だけなので
そのままアップすればどのサーバーでも動くとおもわれる。
但しXreaの場合、どうあっても30分で強制終了するようだ。
コードは現時点で下記の様になっている。

#!/usr/bin/perl

use IO::Socket;
use IO::Select;

$| = 1;

print "Content-type: text/plain\n\n";
$lsn = new IO::Socket::INET(Listen => 1,
LocalPort => 7777,
Reuse => 1,
Proto => 'tcp' )
or die ("x\n");
$sel = new IO::Select( $lsn );

print("o\n");

close(STDOUT);close(STDIN);close(STDERR);
$/ = "\0";
$\ = "\0";
while( @rdy = $sel->can_read ) {
foreach $fh (@rdy ) {

if($fh == $lsn) {
$new = $lsn->accept;
$sel->add($new);
push( @data, fileno($new) . " has joined." . $new->peerhost . " : " .$new->peerport);
}

else {
$input = <$fh>;
chomp $input;
  if ( $input eq 'shut down') {exit};
if ( $input eq '') {
push( @data, fileno($fh) . " has left.");

$sel->remove($fh);
$fh->close;
  if ($sel->count < 2) {exit}
}

else {
$output = fileno($fh) . ": $input : ";
push( @data, $output );

}
}

}

foreach $fh ( @write_ready = $sel->can_write(0) ) {
foreach $line (@data) {
print $fh "$line";
}
}
undef @data;
}


結局

close(STDIN); close(STDOUT);

だけではリクエストした接続はきらず

close(STDIN); close(STDOUT);close(STDERR);

としたら無事にリクエストは終了した。
実はforkもためしたのだがメインルーチンが閉じなかったので同様だった。


or die ("x\n");

というのがPerlらしいコードなのだがいわゆる返り値がエラーの時は
この書き方で終了できる。
このスクリプトはサーバースタートに成功するとoを返し
サーバースタートに失敗するとxを返す。
Flash用にリファインするならば
st=x
のようにした方がいいかもしれない。
1回起動した後に再度リクエストをかけると「x」が返って来る。
このバージョンはポート番号は固定、共用サーバーなので失敗したらシフトするなどの処理が必要だ。


前回からの変更に

$/ = "\0";
$\ = "\0";

もある。
標準出力と入力を閉じた後のループ前に入れている。
$/が入力区切り文字の指定
$\が出力文字の指定で
この2行が入ることでリスト中にヌル文字挿入が不要になるようだ。
但しこれはprint文を使っているからで
入出力の書き方によってはこの部分の変更が必要になる。

基本的に短い移動データとアクションデータのやり取りしか考えていないので
多分これで事足りるだろう。

ループは実質的に接続処理とその他になっている。
つまり
接続処理、またはメッセージ受信処理
メッセージ送信処理
というのが大きな流れだ。

接続処理は

if($fh == $lsn) {
$new = $lsn->accept;
$sel->add($new);
push( @data, fileno($new) . " has joined." . $new->peerhost . " : " .$new->peerport);
}

としているがpush命令があるのは
データ処理は配列にぶち込んでやって後でまとめて全ユーザーに配信しているためである。
いろいろテストした名残でアクセスしたポートとホストを表示しているが
名前のリストにしたり
あるいは同一IPによる接続をさけるかないしは元を切断するかにしたいところだ。

メッセージは切断処理かそれ以外かという形にしている。このコードは効率よくない。
なぜならば終了コマンドが送られる確率は低いからだ。


chomp $input;
  if ( $input eq 'shut down') {exit};
if ( $input eq '') {
push( @data, fileno($fh) . " has left.");

$sel->remove($fh);
$fh->close;
  if ($sel->count < 2) {exit}
  }

までが切断処理になっている。
ユーザーの切断は空のデータでくるのでそれを切断トリガーとしてる。
「shut down」という単語を終了コマンドとして使っている。
また$sel->countはlistenの分を含むのか最初のユーザーが2になるので
ユーザーがいなくなったところでサーバーも終了している。
ループの終わりに全ユーザーがいないかどうかのチェックを入れると
最初のログインの前に終了してしまうがフラグを立てるのも面倒なのでこの場所に置いた。
これはチャット風の様相だが
切断コマンドも含めてコントロールコードは1文字にして配列で制御したほうがいいだろう。

メインのループのところだが

while( @read_ready = $sel->can_read )

は先日も触れたように
While(true)のような無制限ループにはならないらしい。
これはこの直後にカウンターインクリメント命令をつけ
メッセージの後にカウンター値を表記するようにしたところ
接続・切断。メッセージなどがあったときのみ値が変わっていたことから確認した。
ようするにメッセージが無いときのCPU負荷は低いということになるのだが
通常のプログラミングでこの書き方をしたら
無駄にぐるぐるまわるか終了してしまうので解せない。
そうではないということでどうしてループの外に出ないのか疑問ではあるが
とりあえずイベント待ちのような形になってくれるようなので
そういうものだと割切った。

放置しておくと30分でサーバーは切れてしまうので
切れたときに任意のユーザーが再起動リクエストをかけて使う。
サーバー側でそれをやる場合は別スレッドでタイマーをつけるなど必要ではないかと思う。

むやみに汎用性のためにコードが長くなっても無駄だが
とりあえずコード自体のユーザー環境依存度はないので
起動と終了だけちゃんと管理できるようになったから
次段階では具体的にどう使うかということだと思う。
サーバーはユーザーリアクションのデータだけを返し
処理はユーザーが行い
複雑な処理はhttpベースで行う方向でもう少し具体的なものをつめたいが

その前に他の基本素材を押さえる方が先だと思った。
コマンド版PHPでソケットを作ってもしも30分制約が無いならばやってもいいが
そうではないならばとりあえずソケットサーバーはコンテンツを先行させて見ようと思った。


posted by Xo_ox at 22:35| Comment(0) | サーバーサイド手習い | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。