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

oxf4rvwz  于 11个月前  发布在  PHP
关注(0)|答案(1)|浏览(139)

注意事项:这是不是question 25363635的副本我使用php-fpm + apache mod_fcgi + proxy(见下文).connection_aborted()和ignore_user_abort()在我的情况下不工作. TLDR是如何使它们与我的设置- fcgi+proxy一起工作.
我正在使用Server-Sent Events创建一个日志代理。我正在使用这些标题:

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脚本在浏览器关闭连接时退出,或者找到另一种(相对简单的)方法将日志实时转发到浏览器。我不想编写web sockets服务器或安装额外的软件,除非真的没有其他方法。
我也喜欢长轮询的想法,但是如果脚本一直挂在后台,不知道浏览器关闭了连接,它最终会填满允许的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);


任何提示是赞赏!

ny6fqffe

ny6fqffe1#

它seems代理或fcgi做一些缓冲,防止php知道浏览器已经关闭连接。我找不到任何关于如何使代理/mod_fcgi刷新更频繁的信息。

当php直接作为CGI运行时,不存在这个问题,但cgi是低效的。但是服务器发送事件通常启动一次,然后它会运行很长一段时间,这减少了每次脚本启动时启动新进程的代价。
因此,作为一种变通方法,我在vhost配置中这样做了:

<FilesMatch "\.php$">
 SetHandler "proxy:unix:/usr/local/php81/var/run/xmlgen-php-fpm.sock|fcgi://localhost/"
</FilesMatch>
<Proxy "fcgi://localhost/">
ProxySet timeout=600 flushpackets=on
</Proxy>

# CGI Configuration for scripts named sse-*.php
<FilesMatch "^sse-.*\.php$">
  Action php81-cgi /cgi-bin/xmlgen/php81
  SetHandler php81-cgi
</FilesMatch>

字符串
这样,所有以'sse-'开头的php脚本都将通过cgi运行。
因为我花了很多时间在为什么CGI不工作上,这是一个额外的提示,如果你使用suexec(apache 2.4中的SuexecUserGroup),php-cgi必须在doc root中(suexec -V将显示它),并且它必须与SuexecUserGroup中的用户/组相同。实现这一点的一种方法是放置一个 Package 器脚本:

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,使php作为fcgi服务运行知道当浏览器关闭连接。

相关问题