ソケットプログラミングにPHPを使用する場合、 socket_set_blocking()は、ブロッキング動作を制御するためによく使用される関数です。多くの開発者は、学習や実践中の機能についての理解が不足しているため、落とし穴に陥る可能性があり、プログラムのデッドロック、タイムアウト、または正しく対応できないことさえあります。この記事では、これらの一般的な問題の原因とその対応戦略の詳細な分析を提供します。
socket_set_blocking(リソース$ socket、bool $ mode):boolは、ソケットのブロッキングモードまたは非ブロッキングモードを設定するために使用されます。 Trueに設定すると、関連する読み取りおよび書き込み操作は完了まで待ちます。 falseに設定すると、操作が正常に完了するかどうかに関係なく、操作はすぐに戻ります。
ブロッキングモードは、ポーリングの複雑さを減らすことができますが、不適切に処理された場合、次のことにつながる可能性があります。
プログラムスタック(デッドロック)
長期的な閉塞により、サービスのタイムアウトが発生します
マルチスレッド/マルチプロセスプログラムのリソース競争
多くの初心者は、後続のI/O操作への影響を理解せずに、 socket_set_blocking()を使用する場合、ブロッキング状態と非ブロッキング状態をランダムに切り替えます。たとえば、次のコードでは、送信後すぐにブロックに戻り、読み取り中にプログラム全体がブロックされる可能性があります。
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, 'gitbox.net', 80);
socket_set_blocking($socket, false);
socket_write($socket, "GET / HTTP/1.1\r\nHost: gitbox.net\r\n\r\n");
// ここでは、すぐに閉塞に戻ります,デッドロックを引き起こす可能性があります
socket_set_blocking($socket, true);
$response = socket_read($socket, 2048);
解決策: 1つのモードをできるだけ均一に使用してみてください。操作中に非ブロッキングを維持し、 socket_select()と協力して待機処理を行うか、プロセス全体をブロックしますが、タイムアウト時間を明確に設定します。
読み取りにブロッキングモードを使用しますが、サーバーが長い間応答しない場合、または応答データがいっぱいになっていない場合、プロセスは停止します。
$response = socket_read($socket, 2048); // サーバーがデータを送信しない場合,ここでブロックされます
解決策:永続的なブロックを避けるために、合理的なタイムアウトを設定します。次の方法を使用してレセプションタイムアウトを設定します。
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ["sec"=>3, "usec"=>0]);
このようにして、サーバーが応答しなくても、最大3秒間待ってから制御に戻り、無限のハングを避けます。
Socket_Select()は、ブロッキングモードで非ブロッキングロジックを実装するための重要なツールです。準備が整うまで実際の操作なしで、複数のソケットの状態の変更を聞くことができ、複数の接続を処理するサーバー側のプログラムに適しています。例えば:
$read = [$socket];
$write = null;
$except = null;
if (socket_select($read, $write, $except, 5)) {
$data = socket_read($socket, 2048);
}
socket_read()はデータの準備ができた場合にのみ呼び出されるため、この方法はデッドロックを効果的に回避できます。
開発におけるもう1つの一般的な落とし穴は、データを送信した後に応答を待つとき、特にAPIやミドルウェアなどの外部サービスでドッキングする場合、誤ったブロッキング設定がネットワークの遅延によって引き起こされる問題を簡単に増幅できる場合、ブロッキングとタイムアウトが適切に処理されないことです。
gitbox.netでサービスに接続するとします:
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, 'gitbox.net', 12345);
socket_set_blocking($socket, true); // サーバーが遅い場合,待ち続けます
対処方法:書き込みタイムアウト( SO_SNDTIMEO )を設定し、タイムアウトを読み取り( SO_RCVTIMEO ):
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, ["sec"=>2, "usec"=>0]);
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ["sec"=>3, "usec"=>0]);
PHPのソケット関数ファミリとストリームカプセル化( FSOCCKOPEN()など)は混合できますが、2つのブロッキングモードが異なる方法で扱うことに注意してください。 Stream_set_blocking()を使用してソケットリソースを処理しますが、 Socket_*シリーズ関数を使用して読み書きができ、一貫性のない動作があり、デバッグの困難を引き起こす可能性があります。
Stream_socket_client()を使用してstream_set_blocking()とstream_select()を一致させるか、 socket_create()などの基礎となるソケット関数を使用して、混合使用を避けることをお勧めします。