Heim  >  Artikel  >  Backend-Entwicklung  >  Eine eingehende Untersuchung, ob PHP nach dem Beenden des Browsers weiterhin ausgeführt wird

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

*文
*文Original
2017-12-29 18:56:111757Durchsuche

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);

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);

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
}

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);

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);

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);
}

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

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)

第二次往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!

Stellungnahme:
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