浏览器连接关闭时 PHP 脚本不会退出

问题描述 投票:0回答:1

注意:这不是问题25363635的重复我正在使用php-fpm + apache mod_fcgi +代理(见下文)。 connection_aborted() 和ignore_user_abort() 在我的情况下不起作用。 TLDR 是如何让它们与我的设置一起工作 - fcgi+proxy。

我正在使用服务器发送事件创建日志代理。我正在使用这些标题:

header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: close');
然后我通过套接字接收日志行并像这样输出它们:

foreach($lines as $line) { echo "data: $line\n\n"; } flush();
问题是当浏览器关闭时(也尝试使用curl),php 脚本继续运行。这需要内存并且还阻止打开新的服务器套接字来接收日志。
我正在使用 php-fpm (8.1) 和 fcgi 以及 apache 2.4。
Apache 配置看起来像这样:

<FilesMatch "\.php$"> SetHandler "proxy:unix:/usr/local/php81/var/run/website.sock|fcgi://localhost/" </FilesMatch> <Proxy "fcgi://localhost/"> ProxySet timeout=600 </Proxy>
是否可以让 php 脚本在浏览器关闭连接时退出,或者找到另一种(相对简单的)方法将日志实时转发到浏览器。我不想编写网络套接字服务器或安装额外的软件,除非真的没有其他办法。

我也喜欢将长轮询用于其他目的的想法。但是,如果脚本一直挂在后台而不知道浏览器关闭了连接,它最终将填满允许的 fpm 进程数量,并且服务器将不再响应。

由于某种原因,当 max_execution_time 到来时,进程不会退出。它永远持续下去。这可能是由于我如何使用套接字接收日志:

<?php $host = "0.0.0.0"; $port = 1234; header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: close'); echo "data: creating socket. pid ".getmypid().", time limit: ".ini_get('max_execution_time')."\n\n"; flush(); $socket = socket_create(AF_INET, SOCK_STREAM, 0); if(empty($socket)) { $error = socket_strerror(socket_last_error()); die("data: could not create socket: $error\n\n"); } // Set the SO_REUSEADDR option if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { die('data: socket_set_option() failed: ' . socket_strerror(socket_last_error($this->socket))."\n\n"); } $success = @socket_bind($socket, $host, $port); if(!$success) { $error = socket_strerror(socket_last_error()); die("data: could not bind: $error\n\n"); } echo "data: listening for connections\n\n"; flush(); socket_listen($socket); $clientSocket = socket_accept($socket); echo "data: got a connection\n\n"; flush(); socket_set_nonblock($clientSocket); $read = [$clientSocket]; $write = null; $except = null; $buffer = ""; ignore_user_abort(false); // doesn't help while(true) { if (connection_aborted()) { // doesn't help either break; } $changedSockets = $read; $numChanged = socket_select($changedSockets, $write, $except, 0, 800*1000); // 5000 microseconds timeout if($numChanged === false) break; if ($numChanged <= 0) continue; $input = socket_read($clientSocket, 16484); if($input === false) { // script only quts when the connection with log client is broken break; } $buffer .= $input; $lines = explode("\n", $buffer); if(count($lines) > 1) { $buffer = array_pop($lines); //last line may be incomplete foreach($lines as $line) { echo "data: $line\n\n"; } flush(); } if (strpos($buffer, "\n") !== false) { list($line, $buffer) = explode("\n", $buffer, 2); if(empty($line)) echo "data: empty line\n\n"; echo "data: $line\n\n"; flush(); // fastcgi_finish_request(); } } socket_close($clientSocket); socket_close($socket);
任何提示表示赞赏!

php apache server-sent-events long-polling fpm
1个回答
0
投票
它接缝代理或 fcgi 做一些缓冲,防止 php 知道浏览器已关闭连接。 我找不到任何有关如何使 proxy/mod_fcgi 更频繁地刷新的信息。 当直接将php作为CGI运行时,这个问题不存在,但cgi效率低下。然而,服务器发送事件通常启动一次,然后运行很长时间,这减少了每次脚本启动时启动新进程的损失。

因此,作为解决方法,我在虚拟主机配置中执行了此操作:

SetHandler "proxy:unix:/usr/local/php81/var/run/xmlgen-php-fpm.sock|fcgi://localhost/" 代理设置超时 = 600 刷新数据包 = 开启

名为 sse-*.php 的脚本的 CGI 配置

操作 php81-cgi /cgi-bin/xmlgen/php81 SetHandler php81-cgi

这样所有以 'sse-' 开头的 php 脚本都将通过 cgi 运行。

因为我花了很多时间来解释为什么 CGI 在这里不起作用,如果你使用 suexec (apache 2.4 中的 SuexecUserGroup),php-cgi 必须位于文档根目录中(

suexec -V

 将显示它),这是一个额外的提示它必须与 SuexecUserGroup 具有相同的用户/组。实现此目的的一种方法是放置一个包装脚本:

cat /var/www/cgi-bin/<vhostname>/php81 #!/bin/bash PHP_CGI=/usr/local/php81/bin/php-cgi export PHPRC="/webroot/picasse/xmlgen/etc/php.ini" # this line irrelevant for CGI - you can skip it: export PHP_FCGI_MAX_REQUESTS=10000 exec $PHP_CGI -c $PHPRC
并确保 

chown

/
chgrp
 文件具有正确的用户/组和 
chmod +x

我很想跳过这种配置复杂性并摆脱缓冲(无论它发生在哪里)。所以我

仍在等待答案如何配置apache,以便作为fcgi服务运行的php知道浏览器何时关闭连接。

© www.soinside.com 2019 - 2024. All rights reserved.