Socket服务器+事件源魔法断开连接

3
我已经成功实现了下面的方案:一个php套接字服务器脚本向所有连接的客户端脚本广播消息,这些客户端脚本由前端脚本上的事件源监听。因此,基本方案是:server.php ->client.php ->front.php
但我一直在努力解决 client.php 中的这部分内容:
while(TRUE)
{
    $read   = array();
    $read[] = $client_socket;

    if( socket_select($read, $write, $except, 10) === FALSE ){
        $errorcode = socket_last_error();
        $errormsg = socket_strerror($errorcode);
        file_put_contents("select_result.log", "Could not listen on socket : [$errorcode] $errormsg".PHP_EOL);
    }

    if( !($client_message = @socket_read($client_socket, 1024)) ){
        $errorcode = socket_last_error();
        $errormsg = socket_strerror($errorcode);
        file_put_contents("read_result.log", "Couldn't read socket: [$errorcode] $errormsg".PHP_EOL, FILE_APPEND);
    }

    if($client_message == FALSE) {
        $client_message  = "event: keep_alive" . PHP_EOL
                         . "data: keep_alive" . PHP_EOL . PHP_EOL;
    }

    echo $client_message;

    if( ob_get_level() > 0 ) ob_flush();
    flush();
}

当在浏览器中打开一个使用事件源front.php脚本时,它可以正常工作。但是,当我关闭或重新加载浏览器窗口中的front.php时,它也会以某种方式检测到断开连接。下面是从server.php中检测到断开连接的部分代码:
if( !($client_input = @socket_read($client, 1024))) {
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);
    echo "$errorcode".PHP_EOL;
}

if ( $client_input === FALSE ) {
    $socket_key = array_search($client, $client_sockets);
    socket_getpeername($client, $client_address, $client_port);
    socket_close($client);//?!!!
    unset($client_sockets[$socket_key]);
    echo "[$client_address : $client_port disconnected]".PHP_EOL;
}

如您所见,当无法从客户端读取数据时,就会发生这种情况。理论上,当我关闭 front.php 后,client.php 应该仍然在后台的无限循环中工作,因为我没有任何循环中断,但是 - 惊奇!- 它停止了。经过实验,我发现这是由于 ob_flush() 函数造成的。当它被注释掉时,server.php 就无法检测到断开连接(浏览器窗口关闭/重新加载)。怎么可能呢?根据手册,ob_flush() 不返回任何值。也没有发现任何错误...... 我不知道该怎么想,也找不出这是为什么。请帮帮我。


1
“front.php”是包含JavaScript等内容的HTML页面吗?也就是说,为了这个问题,它实际上可以被称为“front.html”吗?(或者,是您的HTML客户端使用EventSource连接到的front.php?)换句话说,在您的浏览器运行的JavaScript中,您是使用new EventSource("client.php")还是new EventSource("front.php") - Darren Cook
你说得对,没错,它可以很容易地被称为 front.html,然后我使用 new EventSource("client.php")client.php 然后打开到 server.php 的套接字连接。 - Damaged Organic
2个回答

1
我假设您正在使用Apache或类似的东西作为client.php的前端。当front.php运行var es = new EventSource('client.php')时,浏览器和Apache之间会创建一个专用的TCP/IP套接字。Apache运行PHP,并告诉它加载和运行client.php。(然后client.php创建一个套接字来侦听来自server.php的消息。)
当浏览器关闭(或您调用es.close(),或由于某种原因浏览器和Apache之间的套接字连接丢失),那么Apache将立即(*)关闭PHP进程(运行client.php的进程)。当该PHP进程消失时,client.php和server.php之间的套接字将被关闭(要么立即关闭,要么在server.php下一次尝试读取/写入它时关闭)。
*:通常是“立即”。有时,如果套接字没有干净地关闭,它可以挂起一段时间(几秒钟,从不超过一分钟)。
我认为你对于 ob_flush() 的观察有些误导性;我的猜测是由于没有调用 ob_flush(),导致某些内容被卡在缓冲区中,这意味着Apache会一直保持PHP进程的活动状态,直到超时时间到达。顺便说一句,我使用 @ob_flush();@flush() 这种写法;ob_flush() 前面的 @ 符号基本上做的就是你对 ob_get_level() 检查所做的(但我模糊记得听说有一种情况下该检查并不总是可靠)。

哇,从你的回答中我完全明白了发生了什么......再一次,你真的帮助了我的项目。干杯:) - Damaged Organic

0

将输出缓冲区刷新到浏览器在最近的Chrome / Firefox版本中不再像预期的那样工作。

您需要某种register_shutdown_func(),但它对于您想要的目的同样不稳定。

将客户端/服务器放入通过浏览器加载的PHP脚本中通常非常不稳定,这是由于HTTP协议的设计。 它不设计为保持连接并以IRC等方式发送消息。 HTTP连接通常在30-60秒后由于许多原因而关闭。

我建议您使用PHP命令行版本用于服务器端,JavaScript用于客户端。

此外,加载PHP的方式(apache中的mod_php?)会影响整个过程。 您打开了多少线程/进程,每个进程持有多少连接……

一些Web服务器会在几秒钟后完成页面加载时终止任何脚本,这很难调试并且很难稳定。

尝试使用JavaScript中的WebRTC / WebSockets,这对于此问题来说更好,并且可以更好地集成到任何HTML5站点中。


感谢您的回复;然而,我已经在各种浏览器上(旧版/新版,包括IE 8+)和两个不同的服务器上测试了这个方案(不包括本地主机)。你有点误解了,因为我是通过命令行运行server.php,使用JavaScript事件源front.php为客户端启动并监听client.php的输出。通过每10秒发送一些数据来保持连接活动(可以更频繁)。你说的都很有道理,但时间很短 - 我无法重新编写所有内容,比如使用WebSockets。只有一个小问题,就是真正困扰我的是ob_flush - Damaged Organic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接