【PHP8.x】stream_set_blocking関数の使い方
stream_set_blocking関数の使い方について、初心者にもわかりやすく解説します。
基本的な使い方
stream_set_blocking関数は、指定されたストリームのブロッキングモードを設定する関数です。ストリームとは、ファイルやネットワーク接続など、データを読み書きするための抽象的な経路のことです。この関数を使用することで、ストリームからのデータ読み込みやデータ書き込みの際に、プログラムがデータの準備を待機するか、それともすぐに制御を返すかを制御できます。
この関数は二つの引数を取ります。最初の引数には、設定を変更したいストリームリソース(例えば、fopen()などで開かれたファイルハンドルやfsockopen()で確立されたネットワークソケット)を指定します。二番目の引数には真偽値(trueまたはfalse)を渡します。trueを指定するとストリームはブロッキングモードになり、falseを指定するとノンブロッキングモードになります。
ブロッキングモードでは、データが完全に読み込まれるか、書き込みが完了するまでプログラムの実行が一時停止します。これは一般的な動作であり、コードをシンプルに保つことができます。一方、ノンブロッキングモードでは、データがまだ準備できていなくても、すぐに処理を続行し、後で改めてデータの読み書きを試みることができます。これにより、データ待ちの時間中に別の処理を実行するなど、より効率的なプログラムの作成が可能になります。例えば、複数のネットワーク接続を同時に監視し、データが届いた接続から順に処理するといった場面でノンブロッキングモードが特に有用です。
この関数は、モード設定が成功した場合にはtrueを、失敗した場合にはfalseを返します。
構文(syntax)
1<?php 2 3stream_set_blocking($stream, $enable_blocking); 4 5?>
引数(parameters)
resource $stream, bool $enable
- resource $stream: ブロッキングモードを設定するストリームリソース
- bool $enable: true を指定するとブロッキングモードを有効にし、false を指定すると無効にするブール値
戻り値(return)
bool
指定されたストリームリソースのブロッキングモードを設定することに成功した場合はTRUEを、失敗した場合はFALSEを返します。
サンプルコード
stream_set_blocking()で非ブロッキングソケット通信する
1<?php 2 3declare(strict_types=1); 4 5/** 6 * 非ブロッキングモードのTCPソケットサーバーを起動し、 7 * stream_set_blocking() の使用例を示します。 8 * 9 * このサーバーは、クライアントからの接続を受け付けた後、 10 * その接続ストリームを非ブロッキングモードに設定します。 11 * これにより、データがクライアントから送信されていなくても、 12 * サーバーはデータの読み取り処理(fread)で待機することなく、 13 * 他の処理(この例では'.'の表示)を続けることができます。 14 * 15 * ■ 実行方法 16 * 1. このファイルを `server.php` として保存します。 17 * 2. ターミナルで `php server.php` を実行してサーバーを起動します。 18 * 3. 別のターミナルを開き、`telnet localhost 8080` または `nc localhost 8080` を実行してサーバーに接続します。 19 * 4. サーバー側のターミナルで、クライアント接続後も `.` が表示され続けることを確認してください。 20 * 5. telnet/nc側で何かテキストを入力してEnterキーを押すと、サーバーがそれを受信して表示します。 21 */ 22function runNonBlockingServerExample(): void 23{ 24 $address = 'tcp://127.0.0.1:8080'; 25 26 // TCPサーバーソケットを作成します。 27 // stream_socket_server()は、接続を待ち受けるためのストリームを作成します。 28 $serverSocket = stream_socket_server($address, $errno, $errstr); 29 30 if (!$serverSocket) { 31 echo "サーバーの作成に失敗しました: ($errno) $errstr\n"; 32 return; 33 } 34 35 echo "サーバーが {$address} で起動しました。クライアントの接続を待っています...\n"; 36 37 // 接続を待ち受けます。stream_socket_accept()はブロッキング関数で、 38 // クライアントが接続してくるまで処理を停止します。 39 $clientSocket = stream_socket_accept($serverSocket, -1); 40 41 if ($clientSocket) { 42 echo "クライアントが接続しました。\n"; 43 44 // ★★★ ここが stream_set_blocking() の使用箇所です ★★★ 45 // 第2引数を `false` に設定し、クライアントソケットを「非ブロッキングモード」にします。 46 // これにより、後続の fread() はデータがなくても待機せず、すぐに処理を返します。 47 $isSet = stream_set_blocking($clientSocket, false); 48 if ($isSet) { 49 echo "ソケットを非ブロッキングモードに設定しました。\n"; 50 } else { 51 echo "非ブロッキングモードへの設定に失敗しました。\n"; 52 fclose($clientSocket); 53 fclose($serverSocket); 54 return; 55 } 56 57 // クライアントとの通信を処理するループ 58 while (true) { 59 // 非ブロッキングモードのため、クライアントからデータが送られてきていなくても 60 // この fread() 関数は待機せず、すぐに空文字列 '' を返します。 61 $data = fread($clientSocket, 1024); 62 63 // データを受信した場合 64 if ($data !== '' && $data !== false) { 65 echo "\n受信: " . trim($data) . "\n"; 66 // 受信したデータをクライアントにエコーバック(送り返す) 67 fwrite($clientSocket, 'サーバーが受信しました: ' . $data); 68 } 69 70 // 接続がクライアント側から閉じられたか確認します。 71 if (feof($clientSocket)) { 72 echo "\nクライアントが切断しました。\n"; 73 break; // ループを抜けます。 74 } 75 76 // データがなくてもループが回り続けていることを示すために `.` を出力します。 77 echo "."; 78 // CPU使用率が100%になるのを防ぐため、少しの間スリープします。 79 usleep(200000); // 0.2秒待機 80 } 81 82 // クライアントソケットを閉じます。 83 fclose($clientSocket); 84 } 85 86 // サーバーソケットを閉じます。 87 fclose($serverSocket); 88 echo "サーバーをシャットダウンします。\n"; 89} 90 91// サーバーを実行します。 92runNonBlockingServerExample();
stream_set_blocking()は、指定したストリームのブロッキングモードを設定、または解除するためのPHP関数です。
第1引数$streamには、ファイルポインタやソケットといった、設定対象のストリームリソースを指定します。第2引数$enableには真偽値を渡し、trueを指定するとストリームはブロッキングモード(デフォルトの動作)に、falseを指定すると非ブロッキングモードになります。
ブロッキングモードでは、fread()などの関数でデータを読み込む際に、データが利用可能になるまで処理を停止して待機します。一方、非ブロッキングモードに設定されている場合、データがなくても処理は停止せず、すぐに関数は制御を返します。
サンプルコードでは、クライアントとの通信用ソケットに対してこの関数を使い、第2引数にfalseを渡して非ブロッキングモードにしています。これにより、fread()はクライアントからのデータ送信を待たずにループ処理を継続できるため、データがなくても「.`」が出力され続けます。
この関数の戻り値は、モードの設定に成功した場合にtrueを、失敗した場合にfalseを返します。
stream_set_blocking関数で非ブロッキングモードを有効にすると、freadなどのデータ読み取り関数はデータがなくても待機せず、すぐに空文字列を返します。このため、ループ処理内で使用するとCPU使用率が100%になる可能性があり、usleepなどで意図的に処理を休ませることが重要です。また、freadは読み取りエラー時にfalseを返すため、空文字列''とは!==演算子で厳密に区別して判定してください。クライアントが接続を切断したことをfeofで検知し、適切にループを終了させる処理も不可欠です。この関数はソケットやファイルなどのストリームリソースに対してのみ適用できます。