Une étude approfondie pour savoir si php continuera à s'exécuter après la fermeture du navigateur

*文
Libérer: 2023-03-18 19:42:01
original
1801 Les gens l'ont consulté

php continuera-t-il à s'exécuter une fois la navigation terminée ? L'éditeur ci-dessous vous indiquera si php continuera à s'exécuter après la fermeture du navigateur. J'espère que cela aide tout le monde.

Prémisse : Ce dont nous parlons ici est la structure typique de lnmp, le mode nginx+php-fpm

Si j'ai un programme php qui s'exécute très lentement, même sleep() dans le code , Ensuite, lorsque le navigateur se connectera au service, un processus php-fpm sera démarré. Cependant, si le navigateur est fermé à ce moment-là, le processus php-fpm sur le serveur continuera-t-il à s'exécuter à ce moment-là ?

Aujourd'hui, nous allons résoudre ce problème.

L'expérience la plus simple

Le moyen le plus simple est de faire une expérience Nous écrivons un programme : utilisez file_put_contents avant et après le sommeil. le journal :

<?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);
Copier après la connexion

Le résultat de l'opération réelle est que lorsque nous fermons le navigateur client pendant que le serveur est en veille, 2222 seront écrits dans le journal.

Cela signifie donc qu'après la fermeture du navigateur, le php côté serveur continuera à fonctionner ?

ignore_user_abort

Lao Wang et Diogin ont rappelé que cela peut être lié à la fonction ignore_user_abort de php liée.

J'ai donc légèrement modifié le code pour qu'il ressemble à ceci :

<?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);
Copier après la connexion

J'ai constaté qu'il n'y avait aucune utilisation de logiciel. Quelle que soit la valeur définie pour ignore_user_abort, il continuera à s'exécuter.

Mais voici une question : qu'est-ce que user_abort ?

Le document indique très clairement l'abandon en mode cli Lorsque le script php est exécuté et que l'utilisateur termine le script, l'abandon sera déclenché. Le script détermine ensuite s'il faut poursuivre l'exécution en fonction de ignore_user_abort.

Mais le document officiel ne décrit pas clairement l'abandon en mode cgi. Il semble que même si le client se déconnecte, PHP en mode cgi ne recevra pas d'abandon.

ignore_user_abort n'a-t-il aucun effet en mode cgi ?

Est-ce un problème de rythme cardiaque ?

La première chose qui vous vient à l'esprit est un problème de rythme cardiaque ? Lorsque nous déconnectons le client du navigateur, cela équivaut à déconnecter la connexion sans fermer le client. Le serveur doit attendre l'arrivée du TCP keepalive avant de le détecter.

D'accord, vous devez d'abord éliminer le problème de keepalive dans les paramètres du navigateur.

Abandonnez le navigateur et écrivez simplement un programme client : une fois que le programme se connecte au service http, il envoie un en-tête et se met en veille pendant 1 seconde avant de fermer activement la connexion. Cependant, ce programme n'a pas le keepalive http. en-tête.

Le programme est le suivant :

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
}
Copier après la connexion

Programme serveur :

<?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);
Copier après la connexion

On constate que c'est toujours pareil, php fonctionnera toujours, que ignore_user_abort soit défini ou non. Continuez l'exécution pour terminer l'intégralité du script. Il semble que ignore_user_abort ne prenne toujours pas effet.

Comment déclencher ignore_user_abort

Alors comment déclencher ignore_user_abort ? Comment le serveur sait-il que ce socket ne peut pas être utilisé ? Lao Wang et Diogin ont demandé si le serveur doit interagir activement avec le socket pour déterminer si le socket peut être utilisé ?

De plus, nous avons également constaté que PHP fournit deux méthodes, connection_status et connection_aborted, qui peuvent toutes deux détecter l'état actuel de la connexion. Ainsi, notre ligne de code de journalisation peut être modifiée en :

file_put_contents(&#39;/tmp/test.log&#39;, &#39;1 connection status: &#39; 
. connection_status() 
. "abort:" 
. connection_aborted() 
. PHP_EOL, FILE_APPEND | LOCK_EX);
Copier après la connexion

Selon le traitement manuel de la connexion, nous pouvons imprimer l'état actuel de la connexion.

Ce qui suit n'a toujours pas de programme qui interagit avec le socket. Nous utilisons echo, et n'oubliez pas d'apporter le flush plus tard pour éliminer l'impact du flush.

Le programme est modifié en

<?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);
}
Copier après la connexion

Très bien, exécutez le client que nous avons écrit plus tôt. Journal d'observation :


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
Copier après la connexion

Enfin, l'abandon a été créé. Le journal montre également que l'état d'abandon pour les prochaines fois est 1.

Mais il y a quelque chose d'étrange ici, pourquoi l'état des 2 premières connexions est-il toujours à 0 (NORMAL).

RST

Nous utilisons Wireshark pour capturer des paquets afin de voir l'ensemble du processus d'interaction entre le client et le serveur

Ceci L'ensemble du processus n'envoie que 14 paquets. Voyons que lorsque le serveur envoie 22222 pour la première fois, le client renvoie RST. Il n’y aura aucune demande de colis ultérieure.

Je comprends donc que le processus d'interaction approximatif entre le client et le serveur est :

Lorsque le serveur envoie 2222 pour la première fois dans la boucle, le client s'est déconnecté, ce qui est renvoyé est un RST, mais ce processus d'envoi est considéré comme une demande réussie. Jusqu'à la deuxième fois que le serveur souhaite effectuer à nouveau une opération d'écriture sur ce socket, ce socket n'effectuera pas de transmission réseau et renvoie directement que l'état de la connexion est abandonné. La situation ci-dessus s'est donc produite. La première fois, le statut 222 était 0 et l'abandon n'est apparu que la deuxième fois.

strace pour vérification

Nous pouvons également utiliser strace php -S XXX pour vérification

Le journal strace de l'ensemble du processus est le suivant :

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)
Copier après la connexion

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

总结

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

至此,问题结了。

相关推荐:

PHP 底层的运行机制与原理

php cgi与fpm关系

PHP简单实现socks5代理服务器

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal