Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【PHP8.x】STREAM_SHUT_RD定数の使い方

STREAM_SHUT_RD定数の使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

STREAM_SHUT_RD定数は、PHPのストリーム操作において、特定のストリームの読み込み側をシャットダウン(停止)する際に使用される定数です。この定数は、主にネットワークソケットなどのストリーム通信において、データ受信の処理を終了させたい場合に利用されます。

具体的には、stream_socket_shutdown() のような関数に引数として渡すことで、指定されたストリームの読み込み機能を無効にすることができます。STREAM_SHUT_RDを適用すると、そのストリームからのさらなるデータの読み込み操作はできなくなります。しかし、ストリームの書き込み側(データ送信)は影響を受けず、引き続きデータを送信することが可能です。

この定数は、双方向通信を行うストリームにおいて、片方向のみの接続終了を制御したい場合に特に有用です。例えば、クライアントがサーバーに対してすべてのリクエストを送信し終え、これ以上サーバーからの応答を待つ必要がないが、サーバーにはまだ処理を継続してほしいといったシナリオで役立ちます。これにより、リソースの効率的な管理や通信プロトコルの適切な終了処理を実現できます。これはPHPの拡張機能の一部として提供されており、低レベルのネットワークプログラミングなどで重要な役割を担います。

構文(syntax)

1<?php
2$shutdown_mode = STREAM_SHUT_RD;
3?>

引数(parameters)

引数なし

引数はありません

戻り値(return)

int

STREAM_SHUT_RD は、ストリームの読み取り側をシャットダウンするために使用される整数定数で、その値は 0 です。

サンプルコード

PHP stream_select で読み込みを停止する

1<?php
2// PHP 8
3
4/**
5 * STREAM_SHUT_RD 定数と stream_select 関数を使用したネットワーク通信のサンプルコードです。
6 *
7 * この関数は単純なTCPサーバーをセットアップし、クライアントからの接続とデータを処理します。
8 * stream_select を使用して複数のソケットの読み込みイベントを効率的に監視し、
9 * クライアントが特定のメッセージを送信した場合に STREAM_SHUT_RD を使って
10 * そのクライアントソケットの読み込み側をシャットダウンする例を示します。
11 *
12 * 【実行方法】
13 * 1. コマンドラインでこのPHPスクリプトを実行します: `php your_script_name.php`
14 * 2. 別のターミナルからクライアントとして接続します。
15 *    例えば、Telnetを使用する場合: `telnet 127.0.0.1 12345`
16 *
17 * 【動作確認】
18 * - クライアントからメッセージを送信すると、サーバーがそれを受信し、エコーバックします。
19 * - クライアントから 'SHUTDOWN_READ' と送信すると、サーバーはそのクライアントからの
20 *   それ以上のデータ読み込みを停止します (STREAM_SHUT_RD の効果)。
21 *   ただし、サーバーからクライアントへのメッセージ送信は可能です。
22 * - クライアントから 'QUIT' と送信すると、クライアントは接続を閉じます。
23 */
24function runStreamSelectAndShutdownExample(): void
25{
26    // サーバーのホストとポートを設定します。
27    $host = '127.0.0.1';
28    $port = 12345;
29
30    // TCPサーバーソケットを作成します。
31    // stream_socket_server は、指定されたアドレスとポートでリッスンするサーバーソケットを生成します。
32    $server = stream_socket_server("tcp://$host:$port", $errno, $errstr);
33
34    if (!$server) {
35        // ソケット作成に失敗した場合のエラー処理です。
36        echo "エラー: サーバーソケットを作成できませんでした - {$errstr} ({$errno})\n";
37        return;
38    }
39
40    echo "TCPサーバーが {$host}:{$port} でリッスンを開始しました。\n";
41    echo "クライアントは 'telnet {$host} {$port}' などで接続してください。\n";
42    echo "  - 'SHUTDOWN_READ' を送信すると、サーバーはそのクライアントからの読み込みを停止します。\n";
43    echo "  - 'QUIT' を送信すると、クライアントは接続を閉じます。\n\n";
44
45    // stream_select で監視するソケットのリスト。最初はサーバーソケットのみを含みます。
46    // stream_select はこの配列の参照を受け取り、イベントが発生したソケットのみを残します。
47    // そのため、ループごとに元のリストをコピーして渡す必要があります。
48    $sockets = [$server];
49
50    // メインのイベントループ
51    while (true) {
52        // 読み込み可能なソケットを監視するためのリストを準備します。
53        $read_sockets = $sockets;
54        // 書き込み可能ソケット、例外発生ソケットのリストは今回の例では使用しません。
55        $write_sockets = null;
56        $except_sockets = null;
57
58        // stream_select 関数は、複数のソケットのI/Oイベントを監視します。
59        // 第4引数にタイムアウト値 (秒) を指定すると、指定時間までイベント発生を待機します。
60        // 0を指定するとすぐに結果を返し、nullを指定するとイベントが発生するまで無期限に待機します。
61        $num_changed_sockets = stream_select($read_sockets, $write_sockets, $except_sockets, 1); // 1秒タイムアウト
62
63        if ($num_changed_sockets === false) {
64            // stream_select 呼び出し中にエラーが発生した場合
65            echo "エラー: stream_select 呼び出しに失敗しました。\n";
66            break; // ループを終了し、サーバーを停止します。
67        } elseif ($num_changed_sockets === 0) {
68            // タイムアウト時間内に何もイベントが発生しなかった場合
69            // echo "1秒間アクティビティがありませんでした。\n";
70            continue; // 次のループへ進みます。
71        }
72
73        // 読み込み可能になったソケットを一つずつ処理します。
74        foreach ($read_sockets as $socket) {
75            if ($socket === $server) {
76                // サーバーソケットが読み込み可能になった場合、新しいクライアント接続要求があることを意味します。
77                // stream_socket_accept は新しいクライアント接続を受け入れ、そのクライアントソケットを返します。
78                $client = stream_socket_accept($server);
79                if ($client) {
80                    echo "クライアントが接続しました: " . stream_socket_get_name($client, true) . "\n";
81                    // クライアントソケットを非ブロッキングモードに設定します。
82                    // stream_select と連携させるためには非ブロッキングモードが推奨されます。
83                    stream_set_blocking($client, false);
84                    $sockets[] = $client; // 新しいクライアントを監視対象リストに追加します。
85                }
86            } else {
87                // クライアントソケットが読み込み可能になった場合、クライアントからデータが送信されたことを意味します。
88                // fread でデータを読み込みます。最大1024バイトを読み込みます。
89                $data = fread($socket, 1024);
90
91                if ($data === false || $data === '') {
92                    // クライアントが切断したか、データが EOF (End Of File) に達した場合
93                    // (例えば、クライアントがソケットを閉じた、またはサーバー側で読み込みをシャットダウンしたなど)
94                    echo "クライアント切断またはEOF: " . stream_socket_get_name($socket, true) . "\n";
95                    // 監視リストからこのソケットを削除し、閉じます。
96                    $key = array_search($socket, $sockets);
97                    if ($key !== false) {
98                        unset($sockets[$key]);
99                    }
100                    fclose($socket);
101                    continue; // 次のソケットを処理します。
102                }
103
104                $data = trim($data); // 受信データの前後の空白を削除します。
105                echo "クライアントからの受信 (" . stream_socket_get_name($socket, true) . "): \"{$data}\"\n";
106
107                // 特定のメッセージ 'SHUTDOWN_READ' を受け取った場合
108                if ($data === 'SHUTDOWN_READ') {
109                    echo "コマンド 'SHUTDOWN_READ' を受信しました。STREAM_SHUT_RD を使用してこのクライアントからの読み込みを停止します。\n";
110                    // stream_socket_shutdown はソケットの読み込み側、書き込み側、または両方をシャットダウンします。
111                    // STREAM_SHUT_RD は、読み込み側のみをシャットダウンすることを指定する定数です。
112                    stream_socket_shutdown($socket, STREAM_SHUT_RD);
113                    fwrite($socket, "サーバーはあなたの読み込みストリームをシャットダウンしました。これ以上メッセージは受信しません。\n");
114
115                    // 読み込み側をシャットダウンした後、このソケットを監視リストから削除します。
116                    // これにより、stream_select はこのソケットを読み込み可能として報告しなくなり、
117                    // 不必要な fread 呼び出しを避けることができます。
118                    // (ただし、このソケットの書き込み側はまだ有効です。)
119                    $key = array_search($socket, $sockets);
120                    if ($key !== false) {
121                        unset($sockets[$key]); // 読み込み監視対象から外す
122                    }
123                    continue; // 次のソケットを処理します。
124                }
125
126                // 特定のメッセージ 'QUIT' を受け取った場合
127                if ($data === 'QUIT') {
128                    echo "クライアントが 'QUIT' を送信しました。接続を閉じます。\n";
129                    fwrite($socket, "Good bye!\n");
130                    // 監視リストからこのソケットを削除し、閉じます。
131                    $key = array_search($socket, $sockets);
132                    if ($key !== false) {
133                        unset($sockets[$key]);
134                    }
135                    fclose($socket);
136                    continue; // 次のソケットを処理します。
137                }
138
139                // その他のメッセージの場合は、クライアントにエコーバック (送り返し) します。
140                fwrite($socket, "サーバーエコー: {$data}\n");
141            }
142        }
143
144        // サーバーソケット以外に監視対象のソケットがなくなった場合
145        // (つまり、すべてのクライアントが切断した場合)、サーバーも終了します。
146        if (count($sockets) === 1 && $sockets[0] === $server) {
147            break;
148        }
149    }
150
151    // サーバーソケットが存在していれば、クリーンアップとして閉じます。
152    if ($server) {
153        fclose($server);
154        echo "サーバーソケットを閉じました。アプリケーションを終了します。\n";
155    }
156}
157
158// サンプルコードの関数を実行します。
159runStreamSelectAndShutdownExample();

このPHPサンプルコードは、STREAM_SHUT_RD定数とstream_select関数を用いて、複数のクライアント接続を処理する簡単なTCPサーバーの実装を示しています。まず、stream_socket_serverでサーバーソケットを作成し、クライアントからの接続を待ち受けます。

stream_select関数は、複数のソケットの中から読み込み可能になったソケットを効率的に判別するために使用されます。この関数は監視対象のソケットリストを引数として受け取り、イベントが発生したソケットのみを残したリストを返します。これにより、サーバーは多数の接続を同時に監視し、必要なI/O処理を適切なタイミングで行うことができます。引数には読み込み、書き込み、例外監視用のソケット配列の参照とタイムアウト値を指定でき、戻り値はイベントが発生したソケットの数を整数で返します。

STREAM_SHUT_RDは、ソケットの読み込み側のみをシャットダウンすることを示す整数定数です。この定数は、stream_socket_shutdown関数と共に使用されます。サンプルでは、クライアントが「SHUTDOWN_READ」というメッセージを送信すると、stream_socket_shutdown($socket, STREAM_SHUT_RD)を実行し、そのクライアントソケットからの今後のデータ読み込みを停止します。これにより、サーバーはそのクライアントからのデータを受信しなくなりますが、サーバーからクライアントへのデータの書き込みは引き続き可能です。このコードは、PHPでのネットワークプログラミングにおける効率的なイベント駆動処理と、ソケットのI/Oストリームを細かく制御する方法を初心者の方に理解いただくための具体的な例となります。

stream_select関数は、監視対象のソケットリストがイベント発生後に更新されるため、メインループごとに元のリストをコピーして渡す必要があります。効率的なネットワーク通信のため、新しいクライアント接続を受け入れたら、必ずstream_set_blockingでソケットを非ブロッキングモードに設定してください。クライアントソケットを閉じる際は、fcloseでソケットを閉じるとともに、監視リストからも忘れずに削除してリソースリークを防ぎましょう。STREAM_SHUT_RDはソケットの読み込み側のみを停止するため、書き込み側は依然として利用可能ですが、読み込み監視リストからは外すべきです。サーバー起動時のエラー処理も適切に行い、予期せぬ障害に備えることが重要です。

PHP ストリームタイムアウトと読み込みシャットダウン

1<?php
2
3/**
4 * PHPストリーム操作のデモンストレーション。
5 * stream_set_timeout 関数と STREAM_SHUT_RD 定数の使用例を示します。
6 * stream_socket_pair を用いて、実際のネットワーク通信をシミュレートする形でストリームの振る舞いを解説します。
7 *
8 * システムエンジニアを目指す初心者が、ソケットストリームのライフサイクルにおける
9 * タイムアウト設定と読み込み側シャットダウンの概念を理解するのに役立ちます。
10 */
11function demonstrateStreamOperations(): void
12{
13    // stream_socket_pair は、2つの接続された双方向ソケットストリームを作成します。
14    // これにより、単一のスクリプト内で擬似的なクライアントとサーバー間の通信をデモンストレーションできます。
15    $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
16    if ($sockets === false) {
17        echo "エラー: ソケットペアの作成に失敗しました。\n";
18        return;
19    }
20
21    $clientSocket = $sockets[0]; // クライアント側のソケットと見立てます
22    $serverSocket = $sockets[1]; // サーバー側のソケットと見立てます
23
24    echo "--- クライアント側操作 ---\n";
25
26    // 1. クライアントからサーバーへメッセージを送信します。
27    $clientMessage = "Hello from client!";
28    fwrite($clientSocket, $clientMessage);
29    echo "クライアントが送信: '{$clientMessage}'\n";
30
31    // 2. クライアント側のストリームにタイムアウトを設定します。
32    // stream_set_timeout は、ストリームに対する読み込み操作の最大待ち時間を設定します。
33    // ここでは、サーバーからの応答を待つ際に2秒が経過したらタイムアウトするように設定しています。
34    stream_set_timeout($clientSocket, 2); // タイムアウト2秒を設定
35    echo "クライアントストリームの読み込みタイムアウトを2秒に設定しました。\n";
36
37    echo "\n--- サーバー側操作 ---\n";
38
39    // 3. サーバーがクライアントからのデータを受信します。
40    $receivedByServer = fread($serverSocket, 1024);
41    echo "サーバーが受信: '{$receivedByServer}'\n";
42
43    // 4. サーバーからクライアントへ応答メッセージを送信します。
44    $serverResponse = "Hello from server!";
45    fwrite($serverSocket, $serverResponse);
46    echo "サーバーが送信: '{$serverResponse}'\n";
47
48    echo "\n--- クライアント側操作 (続き) ---\n";
49
50    // 5. クライアントがサーバーからの応答メッセージを受信します。
51    $receivedByClient = fread($clientSocket, 1024);
52    echo "クライアントが受信: '{$receivedByClient}'\n";
53
54    // 6. クライアント側のストリームの読み込み側をシャットダウンします。
55    // STREAM_SHUT_RD は、stream_socket_shutdown 関数の引数として使用される定数です。
56    // この定数により、指定されたストリームの読み込み側がクローズされ、これ以上データを受信しないことをシステムに通知します。
57    stream_socket_shutdown($clientSocket, STREAM_SHUT_RD);
58    echo "クライアントストリームの読み込み側をシャットダウンしました (定数: STREAM_SHUT_RD)。\n";
59
60    // 7. シャットダウン後に再度読み込みを試みます。
61    // 読み込み側がシャットダウンされているため、データは読み込まれず、即座に空文字列かfalseが返されます。
62    // stream_set_timeout で設定されたタイムアウトは読み込み操作に適用されますが、
63    // 読み込み自体が不可能な状態になっているため、タイムアウトを待つことなく結果が返ります。
64    echo "シャットダウン後に読み込みを試行中...\n";
65    $readAfterShutdown = fread($clientSocket, 1024);
66    if ($readAfterShutdown === false || $readAfterShutdown === '') {
67        echo "データは読み込まれませんでした (読み込み側がシャットダウンされたため、想定通りの結果です)。\n";
68    } else {
69        echo "予期せぬデータが読み込まれました: '{$readAfterShutdown}'\n";
70    }
71
72    // 使用したソケットストリームを閉じます。
73    fclose($clientSocket);
74    fclose($serverSocket);
75    echo "\nソケットを閉じました。\n";
76}
77
78// 関数の実行
79demonstrateStreamOperations();

本サンプルコードは、PHPにおけるストリーム操作の基本的なデモンストレーションを行い、特にstream_set_timeout関数による読み込みタイムアウト設定と、STREAM_SHUT_RD定数を用いたストリームの読み込み側シャットダウンの挙動を解説しています。

まず、stream_socket_pair関数を用いて、単一のスクリプト内で擬似的なクライアントとサーバー間の双方向通信を確立し、ストリームの振る舞いを検証します。次に、stream_set_timeout関数を使用し、クライアント側のストリームに読み込みタイムアウトを2秒と設定します。この関数は、ストリームリソースと秒数(int)を引数に取り、データの読み込みを試みた際に、指定された時間以上待ってもデータが利用可能にならない場合に、操作を中断してタイムアウトを発生させるための設定です。

さらに、STREAM_SHUT_RD定数は、stream_socket_shutdown関数と共に使用され、ソケットストリームの読み込み側をシャットダウンする役割を持ちます。この定数(int)を指定することで、該当するストリームからはこれ以上データを受信しないことをシステムに通知します。サンプルコードでは、クライアント側の読み込み側をシャットダウンした後に、再度データの読み込みを試みています。読み込み側が閉じられているため、データは即座に読み込まれず、空文字列またはfalseが返されることが確認でき、stream_set_timeoutで設定されたタイムアウトを待つことなく結果が得られます。この一連の操作により、ストリームにおけるタイムアウト設定が読み込み操作に与える影響と、特定の定数を用いてストリームの一部を制御する方法について、実践的に理解を深めることができます。

STREAM_SHUT_RD定数は、stream_socket_shutdown関数でストリームの「読み込み側」のみをシャットダウンする際に指定します。これにより、ストリームはデータを受け取れなくなり、それ以降の読み込み操作は即座に空値などを返すようになります。これはストリームを完全に閉じるfcloseとは異なるため注意してください。

stream_set_timeoutは、ストリームからの「読み込み」操作にのみタイムアウトを設定します。設定時間内にデータが来ないと操作が中断されますが、書き込みには影響しません。

これらのストリーム操作後は、必ずfcloseでリソースを解放することが重要です。また、ネットワーク通信では予期せぬエラーが発生しやすいため、常にエラーチェックを行い、処理の成功・失敗を確認する習慣をつけましょう。

関連コンテンツ

関連IT用語

関連プログラミング言語