受け入れブロッキング モデルは比較的古いモデルですが、ブロッキング/非ブロッキング、ロック、タイムアウト再送信など、多くの興味深い知識が含まれています...
<?php set_time_limit(0); # 设置脚本执行时间无限制 class SocketServer { private static $socket; function SocketServer($port) { global $errno, $errstr; if ($port < 1024) { die("Port must be a number which bigger than 1024/n"); } $socket = stream_socket_server("tcp://0.0.0.0:{$port}", $errno, $errstr); if (!$socket) die("$errstr ($errno)"); while ($conn = stream_socket_accept($socket, -1)) { // 这样设置不超时才有用 static $id = 0; # 进程 id static $ct = 0; # 接收数据的长度 $ct_last = $ct; $ct_data = ''; # 接受的数据 $buffer = ''; # 分段读取数据 $id++; echo "Client $id come" . PHP_EOL; # 持续监听 while (!preg_match('{/r/n}', $buffer)) { // 没有读到结束符,继续读 // if (feof($conn)) break; // 防止 popen 和 fread 的 bug 导致的死循环 $buffer = fread($conn, 1024); echo 'R' . PHP_EOL; # 打印读的次数 $ct += strlen($buffer); $ct_data .= preg_replace('{/r/n}', '', $buffer); } $ct_size = ($ct - $ct_last) * 8; echo "[$id] " . __METHOD__ . " > " . $ct_data . PHP_EOL; fwrite($conn, "Received $ct_size byte data./r/n"); fclose($conn); } fclose($socket); } } new SocketServer(2000);
<?php # 日志记录 function debug ($msg) { error_log($msg, 3, './socket.log'); } if ($argv[1]) { $socket_client = stream_socket_client('tcp://0.0.0.0:2000', $errno, $errstr, 30); /* 设置脚本为非阻塞 */ # stream_set_blocking($socket_client, 0); /* 设置脚本超时时间 */ # stream_set_timeout($socket_client, 0, 100000); if (!$socket_client) { die("$errstr ($errno)"); } else { # 填充容器 $msg = trim($argv[1]); for ($i = 0; $i < 10; $i++) { $res = fwrite($socket_client, "$msg($i)"); usleep(100000); echo 'W'; // 打印写的次数 # debug(fread($socket_client, 1024)); // 将产生死锁,因为 fread 在阻塞模式下未读到数据时将等待 } fwrite($socket_client, "/r/n"); // 传输结束符 # 记录日志 debug(fread($socket_client, 1024)); fclose($socket_client); } } else { // $phArr = array(); // for ($i = 0; $i < 10; $i++) { // $phArr[$i] = popen("php ".__FILE__." '{$i}:test'", 'r'); // } // foreach ($phArr as $ph) { // pclose($ph); // } for ($i = 0; $i < 10; $i++) { system("php ".__FILE__." '{$i}:test'"); # 这里等于 php "当前文件" "脚本参数" } }
まず、上記のコード ロジックを説明します。クライアント acceptClient.php はループでデータを送信し、最後にターミネータを送信し、サーバーは accept Server.php を使用してソケット接続を受信し、ターミネータが受信されるまでループでデータを受信し、結果データ (受信バイト数) を返します。サーバーから返されたデータ、ログに書き込みます。ロジックは非常に単純ですが、分析する価値のある状況がいくつかあります:
A> デフォルトでは、phpsocket_client.php テストを実行すると、クライアントは 10 W を出力し、サーバーはいくつかの R を出力し、その後にいくつかの R が出力されます。受信データについては、socket.log にサーバーから返された受信結果データが記録されます。
# この状況は理解しやすいため、繰り返しません。 。次に、telnet コマンドを使用して複数のクライアントを同時に開きます。図に示すように、サーバーは一度に 1 つのクライアントのみを処理することがわかります。他のものは後でキューに入れる必要があります。 "; これが IO ブロックの特徴です。このモードの弱点は明らかであり、効率は非常に低くなります。
B>socket_client.php の 29 行目のコメント コードのみを開いて、php ソケット_クライアント.php テストを再度実行します。クライアントは W を出力し、サーバーも R を出力します。その後、両方のプログラムが停止します。これはなぜでしょうか? ロジックを分析すると、クライアントがターミネータを送信する前にデータをサーバーに返したいためであり、サーバーもターミネータを受信していないため、クライアントにターミネータを要求していることがわかります。 デッドロックの原因。 W と R が 1 つだけ入力されているのは、fread がデフォルトでブロックしているためです。このデッドロックを解決するには、socket_client.php の 17 行目のコメント コードを開き、ソケットの
timeout
C> 14 行のコメントだけを開き、スクリプトをノンブロッキングに設定し、php ソケット_クライアント.php テストを実行します。結果は基本的にケース A と同じです。唯一の違いは、socket.log が記録されていることです。は戻りデータを記録しません。これは、ノンブロッキング モードの場合、クライアントはサーバーからの応答結果を待たずに実行を続行できるためです。デバッグが実行されるとき、読み取られたデータはまだ空であるため、socket.log はも空です。ここでは、ブロッキング モードと非ブロッキング モードで実行されているクライアントの違いがわかります。もちろん、クライアントが結果の受け入れを気にしない場合は、非ブロッキング モードを使用して最大の効率を得ることができます。 D> phpsocket_client.php の実行は、上記のロジックを 10 回連続して実行することです。これに問題はありません。しかし、非常に奇妙なのは、39 ~ 45 行のコードを使用する場合、popen を使用して同時に 10 個のプロセスを開くと、サーバー側で無限ループが発生しますが、これは非常に奇妙です。その後、調査の結果、PHP のソースコードを確認したところ、popen で開いたプロセスによって作成された接続がある限り、fread または Socket_read がエラーとなり、直接空の文字列を返し、無限ループに陥ることが判明しました。 PHP の Popen 関数と fread 関数が C にまったくネイティブではないことが判明し、大量の php_stream_* 実装ロジックが挿入されていますが、当初はそのバグによる Socket 接続の中断が原因であると推定されています。解決策は、socket_server.php の 33 行のコードを開くことです。接続が中断された場合はループから抜け出します。ただし、この方法では大量のデータが失われるため、この問題には特別な注意が必要です。
関連する推奨事項: PHP と Apache の基本的なアプリケーションPHP 正規表現とは何ですか? PHP 正規表現の使用方法 (コード付き)
以上がPHP ネットワーク プログラミングにおける Accept ブロッキング モデルの概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。