ホームページ >バックエンド開発 >PHPチュートリアル >ブラウザが終了した後もphpが実行し続けるかどうかについての詳細な調査
ブラウザを終了した後もphpは実行され続けますか?以下のエディターは、ブラウザーが終了した後も php が実行し続けるかどうかを示します。お役に立てれば幸いです。
前提: ここで話しているのは、典型的な lnmp 構造、nginx+php-fpm モードです
コード内の sleep() さえも実行が非常に遅い php プログラムがある場合、ブラウザが接続したときにサービスを開始すると、php-fpm プロセスが開始されますが、この時点でブラウザが閉じられている場合、サーバー上の php-fpm プロセスはこの時点でも実行され続けますか?
今日はこの問題を解決します。
最も簡単な実験
最も簡単な方法は、プログラムを書くことです: file_put_contents を使用してスリープの前後にログを書き込みます:
<?php file_put_contents('/tmp/test.log', '11111' . PHP_EOL, FILE_APPEND | LOCK_EX); sleep(3); file_put_contents('/tmp/test.log', '2222' . PHP_EOL, FILE_APPEND | LOCK_EX);
実際の操作の結果は次のとおりです。サーバー スリープ プロセス中にクライアント ブラウザを閉じると、2222 がログに書き込まれます。
ということは、ブラウザが閉じられた後も、サーバーサイドのphpは引き続き実行されるということですか?
ignore_user_abort
Lao Wangとdioginは、これがphpのignore_user_abort関数に関連している可能性があることを思い出させてくれました。
そこで、次のようにコードを少し変更しました。
<?php ignore_user_abort(false); file_put_contents('/tmp/test.log', '11111' . PHP_EOL, FILE_APPEND | LOCK_EX); sleep(3); file_put_contents('/tmp/test.log', '2222' . PHP_EOL, FILE_APPEND | LOCK_EX);
ソフトウェアの使用がないことがわかりました。ignore_user_abort がどのような値に設定されていても、実行は継続されます。
しかしここで質問があります: user_abort とは何ですか?
このドキュメントでは、PHP スクリプトが実行され、ユーザーがスクリプトを終了すると、中止がトリガーされることが明確に説明されています。次に、スクリプトは、ignore_user_abort に基づいて実行を継続するかどうかを決定します。
しかし、公式ドキュメントにはCGIモードでの中止について明確に記載されていません。クライアントが切断されても、CGI モードの PHP はアボートを受け取らないようです。
ignore_user_abort は CGI モードでは効果がありませんか?
心拍の問題ですか?
最初に思い浮かぶのは心拍の問題でしょうか?ブラウザ クライアントを切断することは、クライアントを閉じずに接続を切断することと同じであり、サーバーは TCP キープアライブを検出する前に到着するのを待つ必要があります。
まず、ブラウザの設定でキープアライブの問題をトラブルシューティングする必要があります。
ブラウザを放棄してクライアント プログラムを作成します。プログラムは http サービスに接続した後、ヘッダーを送信し、接続をアクティブに閉じる前に 1 秒間スリープします。ただし、このプログラムには http のキープアライブ ヘッダーがありません。
プログラムは次のとおりです:
package main import "net" import "fmt" import "time" func main() { conn, _ := net.Dial("tcp", "192.168.33.10:10011") fmt.Fprintf(conn, "GET /index.php HTTP/1.0\r\n\r\n") time.Sleep(1 * time.Second) conn.Close() return }
サーバープログラム:
<?php ignore_user_abort(false); file_put_contents('/tmp/test.log', '11111' . PHP_EOL, FILE_APPEND | LOCK_EX); sleep(3); file_put_contents('/tmp/test.log', '2222' . PHP_EOL, FILE_APPEND | LOCK_EX);
これは依然として同じであることがわかりました。ignore_user_abortが設定されているかどうかに関係なく、PHPはスクリプト全体を実行し続けます。どうやら、ignore_user_abort はまだ有効になっていないようです。
ignore_user_abort をトリガーする方法
では、ignore_user_abort をトリガーするにはどうすればよいでしょうか?サーバーはこのソケットが使用できないことをどのようにして知るのでしょうか? Lao Wang と Diogin は、ソケットが使用できるかどうかを判断するために、サーバーがソケットと積極的に対話する必要があるかどうかを尋ねました。
さらに、PHP には connection_status と connection_aborted という 2 つのメソッドが用意されており、どちらも現在の接続ステータスを検出できることもわかりました。したがって、コードのログ行は次のように変更できます:
file_put_contents('/tmp/test.log', '1 connection status: ' . connection_status() . "abort:" . connection_aborted() . PHP_EOL, FILE_APPEND | LOCK_EX);
手動接続処理の表示に従って、現在の接続ステータスを出力できます。
以下にはソケットと対話するプログラムがまだありません。echo を使用し、フラッシュの影響を排除するために後でフラッシュを実行することを忘れないでください。
プログラムが
<?php ignore_user_abort(true); file_put_contents('/tmp/test.log', '1 connection status: ' . connection_status() . "abort:" . connection_aborted() . PHP_EOL, FILE_APPEND | LOCK_EX); sleep(3); for($i = 0; $i < 10; $i++) { echo "22222"; flush(); sleep(1); file_put_contents('/tmp/test.log', '2 connection status: ' . connection_status() . "abort:" . connection_aborted(). PHP_EOL, FILE_APPEND | LOCK_EX); }に変更されました
うまくいきました。前に作成したクライアントを実行します。観察記録:
1 connection status: 0abort:0 2 connection status: 0abort:0 2 connection status: 1abort:1 2 connection status: 1abort:1 2 connection status: 1abort:1 2 connection status: 1abort:1 2 connection status: 1abort:1 2 connection status: 1abort:1 2 connection status: 1abort:1 2 connection status: 1abort:1 2 connection status: 1abort:1
ついに中止されました。ログには、今後数回の中止ステータスが 1 であることも示されています。
しかし、ここで何か奇妙なことがあり、最初の2つの接続ステータスのステータスが0(正常)のままなのはなぜですか。
RST
クライアントとサーバー間の対話プロセス全体を確認するために、Wireshark を使用してパケットをキャプチャします
このプロセス全体では 14 パケットのみが送信されます。サーバーが最初に 22222 を送信したときを見てみましょう。 、クライアント 最後は RST を返します。それ以降のパッケージ要求はありません。
ということで、クライアントとサーバー間のおおよその対話プロセスは次のとおりです。
サーバーがループで初めて 2222 を送信すると、クライアントは切断されたため RST を返しますが、これは送信プロセスが考慮されます成功です。サーバーが再度このソケットで書き込み操作を実行する必要があるときまで、このソケットはネットワーク送信を実行せず、接続ステータスが中止であることを直接返します。したがって、上記の状況が発生したのは最初の 222 のステータスが 0 で、中止が発生したのは 2 回目でした。
検証用のstrace
検証にはstrace php -S XXXも使用できます
プロセス全体のstraceログは次のとおりです:
close(5) = 0 lstat("/tmp/test.log", {st_mode=S_IFREG|0644, st_size=49873651, ...}) = 0 open("/tmp/test.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 5 fstat(5, {st_mode=S_IFREG|0644, st_size=49873651, ...}) = 0 lseek(5, 0, SEEK_CUR) = 0 lseek(5, 0, SEEK_CUR) = 0 flock(5, LOCK_EX) = 0 write(5, "1 connection status: 0abort:0\n", 30) = 30 close(5) = 0 sendto(4, "HTTP/1.0 200 OK\r\nConnection: clo"..., 89, 0, NULL, 0) = 89 sendto(4, "111111111", 9, 0, NULL, 0) = 9 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({3, 0}, 0x7fff60a40290) = 0 sendto(4, "22222", 5, 0, NULL, 0) = 5 open("/tmp/test.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 5 fstat(5, {st_mode=S_IFREG|0644, st_size=49873681, ...}) = 0 lseek(5, 0, SEEK_CUR) = 0 lseek(5, 0, SEEK_CUR) = 0 flock(5, LOCK_EX) = 0 write(5, "2 connection status: 0abort:0\n", 30) = 30 close(5) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({1, 0}, 0x7fff60a40290) = 0 sendto(4, "22222", 5, 0, NULL, 0) = -1 EPIPE (Broken pipe) --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=2819, si_uid=0} --- open("/tmp/test.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 5 fstat(5, {st_mode=S_IFREG|0644, st_size=49873711, ...}) = 0 lseek(5, 0, SEEK_CUR) = 0 lseek(5, 0, SEEK_CUR) = 0 flock(5, LOCK_EX) = 0 write(5, "2 connection status: 1abort:1\n", 30) = 30 close(5) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({1, 0}, 0x7fff60a40290) = 0 open("/tmp/test.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 5 fstat(5, {st_mode=S_IFREG|0644, st_size=49873741, ...}) = 0 lseek(5, 0, SEEK_CUR) = 0 lseek(5, 0, SEEK_CUR) = 0 flock(5, LOCK_EX) = 0 write(5, "2 connection status: 1abort:1\n", 30) = 30 close(5) 。。。我们照中看status从0到1转变的地方。 ... sendto(4, "22222", 5, 0, NULL, 0) = 5 ... write(5, "2 connection status: 0abort:0\n", 30) = 30 close(5) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({1, 0}, 0x7fff60a40290) = 0 sendto(4, "22222", 5, 0, NULL, 0) = -1 EPIPE (Broken pipe) --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=2819, si_uid=0} --- open("/tmp/test.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 5 fstat(5, {st_mode=S_IFREG|0644, st_size=49873711, ...}) = 0 lseek(5, 0, SEEK_CUR) = 0 lseek(5, 0, SEEK_CUR) = 0 flock(5, LOCK_EX) = 0 write(5, "2 connection status: 1abort:1\n", 30) = 30 close(5)
第二次往socket中发送2222的时候显示了Broken pipe。这就是程序告诉我们,这个socket已经不能使用了,顺便php中的connection_status就会被设置为1了。后续的写操作也都不会再执行了。
总结
正常情况下,如果客户端client异常推出了,服务端的程序还是会继续执行,直到与IO进行了两次交互操作。服务端发现客户端已经断开连接,这个 时候会触发一个user_abort,如果这个没有设置ignore_user_abort,那么这个php-fpm的程序才会被中断。
至此,问题结了。
相关推荐:
以上がブラウザが終了した後もphpが実行し続けるかどうかについての詳細な調査の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。