ブラウザが終了した後もphpが実行し続けるかどうかについての詳細な調査

*文
リリース: 2023-03-18 19:42:01
オリジナル
1717 人が閲覧しました

ブラウザを終了した後もphpは実行され続けますか?以下のエディターは、ブラウザーが終了した後も php が実行し続けるかどうかを示します。お役に立てれば幸いです。

前提: ここで話しているのは、典型的な lnmp 構造、nginx+php-fpm モードです

​​

コード内の sleep() さえも実行が非常に遅い php プログラムがある場合、ブラウザが接続したときにサービスを開始すると、php-fpm プロセスが開始されますが、この時点でブラウザが閉じられている場合、サーバー上の php-fpm プロセスはこの時点でも実行され続けますか?

今日はこの問題を解決します。

最も簡単な実験

最も簡単な方法は、プログラムを書くことです: file_put_contents を使用してスリープの前後にログを書き込みます:

<?php
file_put_contents(&#39;/tmp/test.log&#39;, &#39;11111&#39; . PHP_EOL, FILE_APPEND | LOCK_EX);
sleep(3);
file_put_contents(&#39;/tmp/test.log&#39;, &#39;2222&#39; . 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(&#39;/tmp/test.log&#39;, &#39;11111&#39; . PHP_EOL, FILE_APPEND | LOCK_EX);
sleep(3);
file_put_contents(&#39;/tmp/test.log&#39;, &#39;2222&#39; . 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(&#39;/tmp/test.log&#39;, &#39;11111&#39; . PHP_EOL, FILE_APPEND | LOCK_EX);
sleep(3);
file_put_contents(&#39;/tmp/test.log&#39;, &#39;2222&#39; . 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(&#39;/tmp/test.log&#39;, &#39;1 connection status: &#39; 
. connection_status() 
. "abort:" 
. connection_aborted() 
. PHP_EOL, FILE_APPEND | LOCK_EX);
ログイン後にコピー

手動接続処理の表示に従って、現在の接続ステータスを出力できます。

以下にはソケットと対話するプログラムがまだありません。echo を使用し、フラッシュの影響を排除するために後でフラッシュを実行することを忘れないでください。

プログラムが

<?php
ignore_user_abort(true);
file_put_contents(&#39;/tmp/test.log&#39;, &#39;1 connection status: &#39; . 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(&#39;/tmp/test.log&#39;, &#39;2 connection status: &#39; . 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 cgi与fpm关系

PHP简单实现socks5代理服务器

以上がブラウザが終了した後もphpが実行し続けるかどうかについての詳細な調査の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!