PHPでソケットプログラミング。オンラインゲームが作りたい(2)
今回はクライアントを同時接続させてチャットっぽいものを作ってみる。
PHPlet使えよってのは禁句です。
前回の記事はこっち。
socket_select()ってのを使えばやりたい事が出来るみたい。
例によってPHPマニュアルのvardhan ( at ) rogers ( dot ) comさんの投稿を参考にとりあえず作ってみた。
なんだか自分でも理解してるのかどうかわかんないけど、出来た。
function?何それ?食べ物?
<?php // エラーのタイプ error_reporting (E_ALL); // サポートされる全てのエラーと警告 // プログラム開始 echo "プログラムを開始しました。\n"; // スクリプトが実行可能な秒数を無制限に設定 set_time_limit (0); // 出力関数のコールが行われるたびに自動的にフラッシュ操作が行う ob_implicit_flush (); // サーバーのIPアドレス $address = '192.168.24.66'; // 使用するポート $port = 10001; // IPv4インターネットプロトコル $domain = AF_INET; // 時系列的、高信頼性、全二重、接続型のバイトスト リーム(何の事やらさっぱり) $type = SOCK_STREAM; // 最大キュー数(どこで役になってるかよく分からん) $backlog = 5; // ソケットを作成する if (($sock = socket_create ($domain, $type, 0)) < 0) { echo "ソケットの作成に失敗しました。" . socket_strerror ($sock) . "\n"; }else{ echo "ソケットを作成しました。\n"; } // ソケットに名前をバインドする if (($ret = socket_bind ($sock, $address, $port)) < 0) { echo "バインドに失敗しました。" . socket_strerror ($ret) . "\n"; }else{ echo "名前をバインドしました。\n"; } // ソケット上で接続をモニタする。 if (($ret = socket_listen ($sock, $backlog)) < 0) { echo "接続待ちに失敗しました。 " . socket_strerror ($ret) . "\n"; }else{ echo "モニター開始しました。\n"; } // クライアントリスト(配列)を作る $clients = array($sock); // break 2 するまで繰り返し while (true) { // クライアントリストをコピっとく $read = $clients; // ブロックの監視 $write = NULL; // 例外の監視 $except = NULL; // 有効時間 $tv_sec = 3600; // マニュアルには0でOKってあるけど、0にするとループして処理落ちする // ソケットの配列を受け取り、 指定した有効時間の間それらの状態が変化するまで待ちます。 if (socket_select($read, $write, $except, $tv_sec) < 1){ // 最初に戻る continue; } // 新規に接続しようとするクライアントがいる場合 if (in_array($sock, $read)) { // ソケットへの接続を許可する。 if ( ($newsock = socket_accept($sock)) < 0) { echo "接続許可に失敗しました。 " . socket_strerror ($newsock) . "\n"; continue; } // クライアントリスト(配列)に追加する $clients[] = $newsock; // 現在の接続人数 $clientCnt = count($clients) - 1; // 接続者のIPを取得 socket_getpeername($newsock, $ip); // 出力する文字 $msg = "==================================\r\n". "サーバーに接続しました。\r\n" . "終わりたいときは'1'押して。\r\n" . "サーバーを止めたい時は'2'押して。\r\n" . "==================================\r\n"; // クライアントへ文字の出力 socket_write($newsock, $msg, strlen($msg)); // 出力する文字 $msg = "「{$ip}さんが来ましたよ。(今{$clientCnt}人)」\r\n"; // 全てのクライアントにログインを報告 foreach ($clients as $send_sock) { // #4を無視 if ($send_sock == $sock ){ continue; } // クライアントへ文字の出力 socket_write ($send_sock, $msg, strlen ($msg)); } // サーバーに文字の出力 echo $msg; // 発言用のforeachが発動しないようにunset $key = array_search($sock, $read); unset($read[$key]); } // 発言のあるクライアントの数だけループ(奇跡の同時入力でも行われない限り大抵1個) foreach ($read as $read_sock) { // クライアントIP socket_getpeername($read_sock, $read_ip); // ソケットから(最大2048バイトまでの)入力データを読込む $buf = socket_read($read_sock, 2048); // 接続が確認出来ない場合 if (FALSE === $buf) { // クライアントリストから削除 $key = array_search($read_sock, $clients); unset($clients[$key]); // 現在の接続人数 $clientCnt = count($clients) - 1; echo "「{$read_ip}さんが切断されました。(今{$clientCnt}人)」\n"; // 次のクライアントへ continue; } // 空白除去して中身が空っぽだったら次のクライアントへ if (!$buf = trim ($buf)) { continue; } // 1だったらクライアントの終了 if ($buf == '1') { // クライアントリストから削除 $key = array_search($read_sock, $clients); unset($clients[$key]); // ソケットを切断する socket_close ($read_sock); // 現在の接続人数 $clientCnt = count($clients) - 1; // 出力する文字 $msg = "「{$read_ip}さんが帰りました。(今{$clientCnt}人)」\r\n"; // 全てのクライアントにログインを報告 // あれ?どっかで同じ処理みたぞ。 foreach ($clients as $send_sock) { // #4を無視 if ($send_sock == $sock ){ continue; } // クライアントへ文字の出力 socket_write ($send_sock, $msg, strlen ($msg)); } // サーバーに文字の出力 echo $msg; // 次のクライアントへ continue; } // 2だったらソケットサーバーの停止 if ($buf == '2') { // ソケットを切断する socket_close ($read_sock); break 2; } // 全てのクライアントに発言を送信 // あれ・・・また同じ・・・ foreach ($clients as $send_sock) { // #4を無視 if ($send_sock == $sock ){ continue; } // 自分の発言の時は改行コードだけ出力する if($send_sock == $read_sock){ socket_write ($send_sock, "\r\n"); continue; } // 出力する文字 $talkback = "{$read_ip}:{$buf}\r\n"; // クライアントへ文字の出力 socket_write ($send_sock, $talkback, strlen ($talkback)); } // サーバーに文字の出力 echo "{$read_ip}:{$buf}\n"; } } // ソケットを閉じる(サーバーの終了) socket_close ($sock); ?>
なんか、接続してない状態でsocket_read()してもfalseが返ってこないんですよね。
だからクライアントがキー操作で切断した時以外ソケットが残ってしまう。
どうしたものかと今後の課題。
本当はずらずらと書いた後にClass化しようかと思ってたんだけど、めんどくさいから止めた。