PHPを使用してソケットベースのサーバーアプリケーションを構築する場合、 socket_accept()は、クライアントからの接続要求を受け入れる非常にコア関数の1つです。ただし、接続管理が適切に処理されていない場合、リソースの漏れや異常なサービス終了を引き起こすのは簡単です。この記事では、これらの接続をどのように作成して、リソースを時間内にリリースできるようにし、サービスの安定性と信頼性を維持することができる方法について説明します。
エレガントなシャットダウンを理解する前に、基本的なソケットサーバーの例をご覧ください。
$address = '0.0.0.0';
$port = 12345;
$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($serverSocket, $address, $port);
socket_listen($serverSocket);
while (true) {
$clientSocket = socket_accept($serverSocket);
// 単純な応答
socket_write($clientSocket, "Hello client!\n");
socket_close($clientSocket);
}
この例では、サーバーは引き続き実行され、接続を受信し、応答します。コードは実行できますが、接続が閉じられ、例外が高い並行性または長期実行中に不合理に処理された場合、次の問題が発生する可能性があります。
ファイル記述子リーク(システムリソースの制限を超える)
接続が閉じられておらず、Time_Waitが蓄積されます
プロセスは終了せざるを得ません(sigintが発生した場合)
PHPのsocket_accept()はブロッキング関数です。つまり、接続リクエストが待機しないことを意味します。この動作は、サービスを終了したり、再起動したり、終了したりするときに、プロセスの通常のシャットダウンを防ぎます。リソースが正しくリリースされていない場合、次の結果が生じる場合があります。
socket_close()実行されていない:接続が閉じられておらず、システムリソースはリリースされません。
スクリプトは異常に終了します。作成された接続がクリーニングされず、ゾンビソケットを残します。
信号割り込み(Ctrl+Cなど):余波は処理されません。
Socket_*シリーズは低レベルのコントロールを提供しますが、PHPのStream_Socket_Server()は、コードの簡素化に適したタイムアウト制御と非ブロッキング動作を簡単に実装できます。
ただし、 Socket_*の使用を主張する場合、次の方法でもエレガントな閉鎖を実現できます。
socket_set_nonblock()は、 socket_accept()のブロックを回避できます。
socket_set_nonblock($serverSocket);
このようにして、ループを使用して、接続があるかどうかを確認し、終了信号を聞くことができます。
declare(ticks = 1);
$running = true;
pcntl_signal(SIGINT, function() use (&$running) {
echo "捕獲 SIGINT,出口の準備...\n";
$running = false;
});
while ($running) {
$clientSocket = @socket_accept($serverSocket);
if ($clientSocket !== false) {
socket_write($clientSocket, "Hello client!\n");
socket_close($clientSocket);
} else {
usleep(100000); // 避ける CPU 占有が高すぎる
}
}
socket_close($serverSocket);
複数のソケットで聞く必要がある場合は、 socket_select()を組み合わせてイベント駆動型モデルを実装できます。
$readSockets = [$serverSocket];
$write = $except = null;
if (socket_select($readSockets, $write, $except, 1) > 0) {
foreach ($readSockets as $sock) {
if ($sock === $serverSocket) {
$clientSocket = socket_accept($serverSocket);
if ($clientSocket !== false) {
socket_write($clientSocket, "Hi\n");
socket_close($clientSocket);
}
}
}
}
Socket_Select()は、タイムアウトを設定したり、時間内に割り込み信号に応答したりできます。これは、より堅牢なサーバープログラムの実装に適しています。
socket_create()およびsocket_accept()によって取得された各リソースがペアになって実行され、 socket_close()が実行されていることを確認してください。よく構成されたソケットサービスのライフサイクルは次のとおりです。
ソケットを初期化します
非ブロッキング/選択を設定します
メインループとプロセスリクエストで接続を受信します
終了信号をキャプチャし、ループを終了します
すべての開いた接続をクリーンアップします
最後に、リスニングソケットを閉じます
信号キャプチャ、非ブロッキングモード、およびリソースクリーニングを実装する完全な例を次に示します。
declare(ticks = 1);
$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($serverSocket, '0.0.0.0', 8000);
socket_listen($serverSocket);
socket_set_nonblock($serverSocket);
$running = true;
pcntl_signal(SIGINT, function() use (&$running) {
echo "終了信号を受け取りました,サービスを閉じる準備をします...\n";
$running = false;
});
while ($running) {
$clientSocket = @socket_accept($serverSocket);
if ($clientSocket) {
socket_write($clientSocket, "訪問してください m66.net!\n");
socket_close($clientSocket);
} else {
usleep(100000);
}
}
socket_close($serverSocket);
echo "サービスはエレガントに閉鎖されています。\n";
PHPが提供するソケットプログラミングインターフェイスは強力ですが、リソース管理の複雑さももたらします。非ブロッキングモードを設定し、終了信号を聞き、 socket_select()およびその他の方法を使用して、PHPで「エレガントシャットダウン」を実現し、リソースの漏れと異常な終了の問題を効果的に回避できます。これらの方法は、長期にわたるサービスを構築するときに特に重要です。
覚えておいてください:ソケットを含む、開くすべてのリソースを常にリリースしてください。