1

基于这个答案,我已经切换到pecl-event库。我现在有:

[root]# php -v
PHP 7.1.12 (cli) (built: Nov 22 2017 08:40:02) ( NTS ) Copyright (c) 1997-2017 The PHP Group Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologieswith Zend OPcache v7.1.12, Copyright (c) 1999-2017, by Zend Technologies 
[root]# php --info | grep event
/etc/php.d/event.ini event libevent2 headers version => 2.1.8-stable
[root]# pecl list
Installed packages, channel pecl.php.net:
=========================================
Package Version State
event   2.3.0   stable

下面的例子表现得很奇怪。如果$loop->run()runme()函数内部调用,它会起作用并调用回调。但是如果$loop->run()从外部调用runme(),它会挂起!

require_once __DIR__.'/../vendor/autoload.php';

$inner = count($argv) > 1;

$loop = new \React\EventLoop\ExtEventLoop();
//$loop = new \React\EventLoop\StreamSelectLoop();

runme($loop, $inner);

if (!$inner) {
    echo "Outer start\n";
    $loop->run();
}

function runme(\React\EventLoop\LoopInterface $loop, $inner)
{
    $contextOpts = [];
    $flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
    $context = stream_context_create($contextOpts);
    $socket = stream_socket_client('tcp://127.0.0.1:3306', $errno, $errstr, 0, $flags, $context);
    stream_set_blocking($socket, 0);

    $loop->addWriteStream($socket, function ($socket) use ($loop) {
        echo "done  ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
        $loop->removeWriteStream($socket);
    });

    if ($inner) {
        echo "Inner start\n";
        $loop->run();
    }

    echo "Exit runme\n";
}

运行结果:

[root@vultr Scraper]# php ./tests/test.php --inner
Inner start
done  false
Exit runme
[root@vultr Scraper]# php ./tests/test.php 
Exit runme
Outer start
...............HANGING HERE...........

我错过了什么还是其中一个库/PHP有问题?有人有运行 php7.1 + react + libevent 的经验吗?

更新:================================================ =====================

我用最新的“react/socket”库“0.8.6”进行了测试。

require_once __DIR__.'/vendor/autoload.php';

$inner = count($argv) > 1;

$loop = new \React\EventLoop\ExtEventLoop();

$connector = new React\Socket\Connector($loop);

runme($loop, $connector, $inner);

if (!$inner) {
    echo "Outer start\n";
    $loop->run();
}

function runme(\React\EventLoop\LoopInterface $loop, React\Socket\Connector $connector, $inner)
{
    $connector->connect('tcp://127.0.0.1:3306')->
    then(function (\React\Socket\ConnectionInterface $conn) {
        echo ("Hello MySQL!\n");
        $conn->close();
    },function ($e) {
        echo ("Bye MySQL!\n");
    })->done();

    if ($inner) {
        echo "Inner start\n";
        $loop->run();
    }

    echo "Exit runme\n";
}

它工作正常并返回:

$ php ./testMysql.php 
Exit runme
Outer start
Hello MySQL!
$ php ./testMysql.php  --inner
Inner start
Hello MySQL!
Exit runme

但是,如果您进入 \React\Socket\TcpConnector::waitForStreamOnce() 并在新的 Promise 对象中删除 $canceller 函数,如下所示,它会再次挂起。看起来它可以在最新版本的 react 中工作,因为 socket 没有以明显的方式存储,实际上类似于 v0.4.6 中的代码。

private function waitForStreamOnce($stream)
    {
        $loop = $this->loop;

        return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
            $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
                $loop->removeWriteStream($stream);

                // The following hack looks like the only way to
                // detect connection refused errors with PHP's stream sockets.
                if (false === stream_socket_get_name($stream, true)) {
                    fclose($stream);

                    $reject(new \RuntimeException('Connection refused'));
                } else {
                    $resolve(new Connection($stream, $loop));
                }
            });
        });
    }



$ php ./testMysql.php  --inner
Inner start
.....HANGING
$ php ./testMysql.php 
Exit runme
Outer start
...HANGING
4

2 回答 2

2

问题是该$socket变量在返回时被破坏runme()(就像任何本地 PHP 变量一样!)。结果,在这个套接字上打开的连接是关闭的

事件扩展尽最大努力防止内存泄漏,因此如果可能,它不会存储对用户变量的引用。特别是,所有接受套接字资源的方法(Event::__construct例如)仅从输入变量中检索底层数字文件描述符。用户实际上负责保持这些变量处于活动状态

以下脚本通过移动$socket到全局范围来解决此问题。

require_once 'vendor/autoload.php';

$inner = count($argv) > 1;
$loop = new \React\EventLoop\ExtEventLoop();
$socket = init_socket();

runme($loop, $socket, $inner);

if (!$inner) {
    echo "Outer start\n";
    $loop->run();
}

function init_socket()
{
    $contextOpts = [];
    $flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
    $context = stream_context_create($contextOpts);
    $socket = stream_socket_client('tcp://test.local:80', $errno, $errstr, 0, $flags, $context);
    stream_set_blocking($socket, 0);
    return $socket;
}

function runme(\React\EventLoop\LoopInterface $loop, $socket, $inner)
{
    $loop->addWriteStream($socket, function ($socket) use ($loop) {
        echo "done  ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
        $loop->removeWriteStream($socket);
    });

    if ($inner) {
        echo "Inner start\n";
        $loop->run();
    }

    echo "Exit runme\n";
}

在实际应用程序中,您可能会将其存储$socket为类成员变量。

于 2017-11-30T04:30:06.277 回答
0

Hey ReactPHP core developer here, just looked into your script and I can reproduce this locally so I'm going to file an issue (even though this might be out of our scope) for this.

require_once __DIR__.'/../vendor/autoload.php';

$inner = count($argv) > 1;

$loop = new \React\EventLoop\ExtEventLoop();
//$loop = new \React\EventLoop\StreamSelectLoop();

runme($loop, $inner);

    $contextOpts = [];
    $flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
    $context = stream_context_create($contextOpts);
    $socket = stream_socket_client('tcp://127.0.0.1:3306', $errno, $errstr, 0, $flags, $context);
    stream_set_blocking($socket, 0);

    $loop->addWriteStream($socket, function ($socket) use ($loop) {
        echo "done  ".(false === stream_socket_get_name($socket, true) ? 'false' : 'true')."\n";
        $loop->removeWriteStream($socket);
    });

    if ($inner) {
        echo "Inner start\n";
        $loop->run();
    }

    echo "Exit runme\n";

But alternatively I suggest you look into our socket component for handling connections.

Which could look something like:

$loop = new \React\EventLoop\ExtEventLoop();
$connector = new React\Socket\Connector($loop);

$connector->connect('tcp://127.0.0.1:3306')->then(function (ConnectionInterface $conn) use ($loop) {
    $conn->write("Hello MySQL!\n");
});

$loop->run();
于 2017-11-29T16:38:50.140 回答