Eine eingehende Untersuchung, ob PHP nach dem Beenden des Browsers weiterhin ausgeführt wird

*文
Freigeben: 2023-03-18 19:42:01
Original
1794 Leute haben es durchsucht

Wird PHP nach dem Beenden des Browsers weiterhin ausgeführt? Der folgende Editor zeigt Ihnen, ob PHP nach dem Beenden des Browsers weiterhin ausgeführt wird. Ich hoffe, es hilft allen.

Prämisse: Worüber wir hier sprechen, ist die typische LNMP-Struktur, Nginx+php-fpm-Modus

Wenn ich ein PHP-Programm habe, das sehr langsam ausgeführt wird, sogar sleep() im Code Wenn der Browser dann eine Verbindung zum Dienst herstellt, wird ein PHP-FPM-Prozess gestartet. Wenn der Browser jedoch zu diesem Zeitpunkt geschlossen wird, wird der PHP-FPM-Prozess auf dem Server zu diesem Zeitpunkt weiterhin ausgeführt?

Heute werden wir dieses Problem lösen.

Das einfachste Experiment

Der einfachste Weg ist, ein Experiment durchzuführen: Verwenden Sie file_put_contents vor und nach dem Schlafen das Protokoll:

<?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);
Nach dem Login kopieren

Das Ergebnis des eigentlichen Vorgangs ist, dass 2222 in das Protokoll geschrieben wird, wenn wir den Client-Browser schließen, während der Server schläft.

Das bedeutet also, dass nach dem Schließen des Browsers das serverseitige PHP weiterhin ausgeführt wird?

ignore_user_abort

Lao Wang und Diogin erinnerten daran, dass dies möglicherweise damit zusammenhängt Bezogen auf die Funktion „ignore_user_abort“ von PHP.

Also habe ich den Code leicht geändert, so dass er so aussieht:

<?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);
Nach dem Login kopieren

Ich habe festgestellt, dass keine Software verwendet wird, egal auf welchen Wert „ignore_user_abort“ eingestellt ist, die Ausführung wird fortgesetzt.

Aber hier ist eine Frage: Was ist user_abort?

Das Dokument macht den Abbruch im CLI-Modus sehr deutlich. Wenn das PHP-Skript ausgeführt wird und der Benutzer das Skript beendet, wird ein Abbruch ausgelöst. Das Skript bestimmt dann anhand von „ignore_user_abort“, ob die Ausführung fortgesetzt werden soll.

Aber das offizielle Dokument beschreibt den Abbruch im CGI-Modus nicht eindeutig. Es fühlt sich so an, als würde PHP im CGI-Modus keinen Abbruch erhalten, selbst wenn der Client die Verbindung trennt.

Hat „ignore_user_abort“ im CGI-Modus keine Auswirkung?

Ist es ein Herzschlagproblem?

Das erste, was mir in den Sinn kommt, ist ein Herzproblem? Wenn wir den Browser-Client trennen, entspricht dies dem Trennen der Verbindung, ohne den Client zu schließen. Der Server muss auf das Eintreffen des TCP-Keepalive warten, bevor er es erkennt.

Okay, Sie müssen zuerst das Keepalive-Problem in den Browsereinstellungen beseitigen.

Verlassen Sie den Browser und schreiben Sie einfach ein Client-Programm: Nachdem das Programm eine Verbindung zum HTTP-Dienst hergestellt hat, sendet es einen Header und schläft eine Sekunde lang, bevor es die Verbindung aktiv schließt. Dieses Programm verfügt jedoch nicht über das HTTP-Keepalive Kopfzeile.

Das Programm ist wie folgt:

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
}
Nach dem Login kopieren

Serverprogramm:

<?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);
Nach dem Login kopieren

Es wurde festgestellt, dass es immer noch dasselbe ist, PHP funktioniert weiterhin, unabhängig davon, ob „ignore_user_abort“ festgelegt ist oder nicht. Setzen Sie die Ausführung fort, um das gesamte Skript abzuschließen. Es scheint, dass „ignore_user_abort“ immer noch keine Wirkung zeigt.

Wie löst man „ignore_user_abort“ aus?

Wie löst man „ignore_user_abort“ aus? Woher weiß der Server, dass dieser Socket nicht verwendet werden kann? Lao Wang und Diogin fragten, ob der Server aktiv mit dem Socket interagieren muss, um festzustellen, ob der Socket verwendet werden kann.

Darüber hinaus haben wir festgestellt, dass PHP zwei Methoden bereitstellt: „connection_status“ und „connection_aborted“, die beide den aktuellen Verbindungsstatus erkennen können. Daher kann unsere Protokollierungscodezeile wie folgt geändert werden:

file_put_contents(&#39;/tmp/test.log&#39;, &#39;1 connection status: &#39; 
. connection_status() 
. "abort:" 
. connection_aborted() 
. PHP_EOL, FILE_APPEND | LOCK_EX);
Nach dem Login kopieren

Gemäß der manuellen Verbindungsverarbeitung können wir den aktuellen Verbindungsstatus ausdrucken.

Im Folgenden fehlt noch ein Programm, das mit dem Socket interagiert. Wir verwenden Echo und denken daran, Flush später zu bringen, um die Auswirkungen von Flush zu beseitigen.

Das Programm wurde geändert in

<?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);
}
Nach dem Login kopieren

Sehr gut, führen Sie den Client aus, den wir zuvor geschrieben haben. Beobachtungsprotokoll:


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
Nach dem Login kopieren

Endlich wurde ein Abbruch erstellt. Das Protokoll zeigt auch, dass der Abbruchstatus für die nächsten Male 1 ist.

Aber hier gibt es etwas Seltsames, warum ist der Status der ersten 2 Verbindungsstatus immer noch 0 (NORMAL).

RST

Wir verwenden Wireshark, um Pakete zu erfassen und den gesamten Interaktionsprozess zwischen dem Client und dem Server zu sehen

Dies Der gesamte Prozess sendet nur 14 Pakete. Sehen wir uns an, dass der Client RST zurückgibt, wenn der Server zum ersten Mal 22222 sendet. Es erfolgen keine weiteren Paketanfragen.

Ich verstehe also, dass der ungefähre Interaktionsprozess zwischen dem Client und dem Server wie folgt aussieht:

Wenn der Server zum ersten Mal in der Schleife 2222 sendet, hat der Client die Verbindung getrennt. Was zurückgegeben wird, ist ein RST, dieser Sendevorgang wird jedoch als erfolgreiche Anfrage gewertet. Bis der Server zum zweiten Mal erneut einen Schreibvorgang für diesen Socket ausführen möchte, führt dieser Socket keine Netzwerkübertragung durch und gibt direkt zurück, dass der Verbindungsstatus abgebrochen ist. Die obige Situation trat also beim ersten Mal auf, als 222 den Status 0 hatte, und der Abbruch trat erst beim zweiten Mal auf.

strace zur Überprüfung

Wir können zur Überprüfung auch strace php -S XXX verwenden

Das Strace-Protokoll des gesamten Prozesses lautet wie folgt:

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)
Nach dem Login kopieren

第二次往socket中发送2222的时候显示了Broken pipe。这就是程序告诉我们,这个socket已经不能使用了,顺便php中的connection_status就会被设置为1了。后续的写操作也都不会再执行了。

总结

正常情况下,如果客户端client异常推出了,服务端的程序还是会继续执行,直到与IO进行了两次交互操作。服务端发现客户端已经断开连接,这个 时候会触发一个user_abort,如果这个没有设置ignore_user_abort,那么这个php-fpm的程序才会被中断。

至此,问题结了。

相关推荐:

PHP 底层的运行机制与原理

php cgi与fpm关系

PHP简单实现socks5代理服务器

Das obige ist der detaillierte Inhalt vonEine eingehende Untersuchung, ob PHP nach dem Beenden des Browsers weiterhin ausgeführt wird. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage