ReactPHP实践


ReactPHP是事件驱动的,非阻塞I/O的PHP,它和Node.js都是采用单进程和单线程的方式,适用于I/O密集型,不适用于计算密集型。


引言

ReactPHP是事件驱动的,非阻塞I/O的PHP,它和Node.js都是采用单进程和单线程的方式, 适用于I/O密集型,不适用于计算密集型。

React下有好多库,比较常用的有:

  • react/event-loop -ReactPHP的核心响应事件循环,这个库可用于事件I/O
  • react/stream -ReactPHP中用于非阻塞I/O的事件驱动可读写流
  • react/promise -Promise是一个为PHP实现CommonJS Promises/a的库
  • react/promise-timer -在ReactPHP之上构建的Promises超时简单实现
  • react/promise-stream -ReactPHP的Promise-land和Stream-land之间缺少的链接
  • react/cache -用于ReactPHP的异步、基于Promise的缓存接口
  • react/dns -ReactPHP的异步DNS解析程序
  • react/socket -用于ReactPHP的异步流式明文TCP/IP和安全TLS套接字服务器和客户端连接
  • react/http -事件驱动,ReactPHP的 streaming HTTP 客户端和服务器实现
  • react/http-client -事件驱动,streaming HTTP客户端

看一下依赖关系。

react/event-loop:

"autoload": {
    "psr-4": {
        "React\\EventLoop\\": "src"
    }
},

react/stream:

"require": {
    "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5"
},
"autoload": {
    "psr-4": {
        "React\\Stream\\": "src"
    }
},

react/promise:

"autoload": {
    "psr-4": {
        "React\\Promise\\": "src/"
    },
    "files": [
        "src/functions_include.php"
    ]
},

react/promise-timer:

"require": {
    "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
    "react/promise": "^2.7.0 || ^1.2.1"
},
"type": "library",
"autoload": {
    "psr-4": {
        "React\\Promise\\Timer\\": "src/"
    },
    "files": [
        "src/functions_include.php"
    ]
},

react/promise-stream:

"require": {
    "react/promise": "^2.1 || ^1.2",
    "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6"
},
"require-dev": {
    "clue/block-react": "^1.0",
    "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
    "react/promise-timer": "^1.0"
},
"autoload": {
    "psr-4": {
        "React\\Promise\\Stream\\": "src/"
    },
    "files": [
        "src/functions_include.php"
    ]
},

react/cache:

"require": {
    "react/promise": "~2.0|~1.1"
},
"autoload": {
    "psr-4": {
        "React\\Cache\\": "src/"
    }
},

react/dns:

"require": {
    "react/cache": "^1.0 || ^0.6 || ^0.5",
    "react/event-loop": "^1.0 || ^0.5",
    "react/promise": "^2.7 || ^1.2.1",
    "react/promise-timer": "^1.2"
},
"require-dev": {
    "clue/block-react": "^1.2"
},
"autoload": {
    "psr-4": {
        "React\\Dns\\": "src"
    }
},

react/socket:

"require": {
    "react/dns": "^1.0 || ^0.4.13",
    "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
    "react/promise": "^2.6.0 || ^1.2.1",
    "react/promise-timer": "^1.4.0",
    "react/stream": "^1.1"
},
"require-dev": {
    "clue/block-react": "^1.2"
},
"autoload": {
    "psr-4": {
        "React\\Socket\\": "src"
    }
},

react/http:

"require": {
    "react/promise": "^2.3 || ^1.2.1",
    "react/promise-stream": "^1.1",
    "react/socket": "^1.0 || ^0.8.3",
    "react/stream": "^1.0 || ^0.7.1"
},
"require-dev": {
    "clue/block-react": "^1.1"
},
"autoload": {
    "psr-4": {
        "React\\Http\\": "src"
    }
},

react/http-client:

"require": {
    "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
    "react/promise": "^2.1 || ^1.2.1",
    "react/socket": "^1.0 || ^0.8.4",
    "react/stream": "^1.0 || ^0.7.1"
},
"require-dev": {
    "clue/block-react": "^1.2",
    "react/promise-stream": "^1.1"
},
"autoload": {
    "psr-4": {
        "React\\HttpClient\\": "src"
    }
},

在上面多次看到clue/block-react库:一个轻量级库,简化了在传统的阻塞环境中集成为ReactPHP构建的异步组件。 使用可以看这里

系列文章: ReactPHP Series

官方库说明

ReactPHP是非阻塞的。使用阻塞I/O 进程。事件循环基于reactor模式(因此而得名),并受到EventMachine(Ruby)、Twisted(Python)和Node.js(V8)的鼓舞。

Reactor模式详解

react/http

这个HTTP库,基于ReactPHP的Socket和EventLoop组件,为HTTP客户端和服务器提供可重用的实现。 它的客户端组件允许您同时发送任意数量的异步HTTP / HTTPS请求。 它的服务器组件使您可以构建纯文本HTTP和安全的HTTPS服务器, 以接受来自HTTP客户端(例如Web浏览器)的传入HTTP请求。 该库为上面这些提供了异步的流式传输方式,因此您可以处理多个并发的HTTP请求而不会阻塞。

  • 客户端实现
<?php
use React\Http\Browser;
use Psr\Http\Message\ResponseInterface;

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

$loop = React\EventLoop\Factory::create();
$client = new React\Http\Browser($loop);

$client->get('http://www.google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump($response->getHeaders(), (string)$response->getBody());
});

$loop->run();
  • 客户端同时发起多个请求
<?php

use React\Http\Browser;
use Psr\Http\Message\ResponseInterface;

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

$loop = React\EventLoop\Factory::create();
$client = new Browser($loop);

$client->head('http://www.github.com/clue/http-react')->then(function (ResponseInterface $response) {
    var_dump($response->getHeaders(), (string)$response->getBody());
});

$client->get('http://google.com/')->then(function (ResponseInterface $response) {
    var_dump($response->getHeaders(), (string)$response->getBody());
});

$client->get('http://www.lueck.tv/psocksd')->then(function (ResponseInterface $response) {
    var_dump($response->getHeaders(), (string)$response->getBody());
});

$loop->run();

同时请求多个url,第一个请求完成后立即返回,取消所有其他请求:

<?php

// concurrently request a number of URLs.
// return immediately once the first is completed, cancel all others.

use React\Http\Browser;
use Psr\Http\Message\ResponseInterface;

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

$loop = React\EventLoop\Factory::create();
$client = new Browser($loop);

$promises = array(
    $client->head('http://www.github.com/clue/http-react'),
    $client->get('https://httpbin.org/'),
    $client->get('https://google.com'),
    $client->get('http://www.lueck.tv/psocksd'),
    $client->get('http://www.httpbin.org/absolute-redirect/5')
);

React\Promise\any($promises)->then(function (ResponseInterface $response) use ($promises) {
    // first response arrived => cancel all other pending requests
    foreach ($promises as $promise) {
        $promise->cancel();
    }

    var_dump($response->getHeaders());
    echo PHP_EOL . $response->getBody();
});

$loop->run();
  • 客户端post请求
<?php

use React\Http\Browser;
use Psr\Http\Message\ResponseInterface;

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

$loop = React\EventLoop\Factory::create();
$client = new Browser($loop);

$data = array(
    'name' => array(
        'first' => 'Alice',
        'name' => 'Smith'
    ),
    'email' => 'alice@example.com'
);

$client->post(
    'https://httpbin.org/post',
    array(
        'Content-Type' => 'application/json'
    ),
    json_encode($data)
)->then(function (ResponseInterface $response) {
    echo (string)$response->getBody();
}, 'printf');

$loop->run();
  • 客户端代理 客户端Http连接
<?php

// not already running a HTTP CONNECT proxy server?
// Try LeProxy.org or this:
//
// $ php examples/72-server-http-connect-proxy.php 8080
// $ php examples/11-client-http-connect-proxy.php

use React\Http\Browser;
use Clue\React\HttpProxy\ProxyConnector as HttpConnectClient;
use Psr\Http\Message\ResponseInterface;
use React\EventLoop\Factory as LoopFactory;
use React\Socket\Connector;

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

$loop = LoopFactory::create();

// create a new HTTP CONNECT proxy client which connects to a HTTP CONNECT proxy server listening on localhost:8080
$proxy = new HttpConnectClient('127.0.0.1:8080', new Connector($loop));

// create a Browser object that uses the HTTP CONNECT proxy client for connections
$connector = new Connector($loop, array(
    'tcp' => $proxy,
    'dns' => false
));
$browser = new Browser($loop, $connector);

// demo fetching HTTP headers (or bail out otherwise)
$browser->get('https://www.google.com/')->then(function (ResponseInterface $response) {
    echo RingCentral\Psr7\str($response);
}, 'printf');

$loop->run();
  • http服务端实现
<?php
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server(8080, $loop);

$server = new React\Http\Server($loop, function (Psr\Http\Message\ServerRequestInterface $request) {
    return new React\Http\Message\Response(
        200,
        array(
            'Content-Type' => 'text/plain'
        ),
        "Hello World!\n"
    );
});

$server->listen($socket);

$loop->run();

react/socket

  • 客户端代理 客户端Socket连接
<?php

// not already running a SOCKS proxy server?
// Try LeProxy.org or this: `ssh -D 1080 localhost`

use React\Http\Browser;
use Clue\React\Socks\Client as SocksClient;
use Psr\Http\Message\ResponseInterface;
use React\EventLoop\Factory as LoopFactory;
use React\Socket\Connector;

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

$loop = LoopFactory::create();

// create a new SOCKS proxy client which connects to a SOCKS proxy server listening on localhost:1080
$proxy = new SocksClient('127.0.0.1:1080', new Connector($loop));

// create a Browser object that uses the SOCKS proxy client for connections
$connector = new Connector($loop, array(
    'tcp' => $proxy,
    'dns' => false
));
$browser = new Browser($loop, $connector);

// demo fetching HTTP headers (or bail out otherwise)
$browser->get('https://www.google.com/')->then(function (ResponseInterface $response) {
    echo RingCentral\Psr7\str($response);
}, 'printf');

$loop->run();
  • socket服务端实现
<?php
$loop = React\EventLoop\Factory::create();

$socket = new React\Socket\Server($loop);
$socket->on('connection', function ($conn) {
    echo 'New client !';

    $conn->on('data', function ($data) use ($conn) {
        $conn->write("Wow, some data, such cool\n");
        $conn->close();
    });
});
$socket->listen(1337);

$loop->run();

使用socket实现Http服务器:

<?php

// $ composer require react/http react/socket # install example using Composer
// $ php example.php # run example on command line, requires no additional web server

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

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    return React\Http\Message\Response::plaintext(
        "Hello World!\n"
    );
});

$socket = new React\Socket\SocketServer('127.0.0.1:8080');
$http->listen($socket);

echo "Server running at http://127.0.0.1:8080" . PHP_EOL;

ReactPHP爬虫

看一下下面的异步实现:

ReactPHP 爬虫实战:下载整个网站的图片

国外地址

但是保存图片时,file_put_contents()是同步阻塞的,用了这个函数,上面的异步非阻塞实现就废了:变成同步阻塞了, 要想异步非阻塞实现,我们保存图片等就要用 reactphp/filesystem 实现。

这个也可以看下: Fast Web Scraping With ReactPHP






参考资料

ReactPHP官网 https://reactphp.org/

ReactPHP Github https://github.com/reactphp

Packages from react https://packagist.org/packages/react/

react/event-loop Packagist https://packagist.org/packages/react/event-loop

React PHP如何处理异步非阻塞I / O? https://www.jb51.cc/react/300894.html

ReactPHP-event-loop使用文档(中文翻译) https://blog.csdn.net/zeroking_vip/article/details/82870424

ReactPHP简介 https://www.wangzhiguang.com.cn/563.html

reactphp/http 用例 https://github.com/reactphp/http/tree/7b08b2c115f17e50204aa0d980b206dad3e981fb/examples

react/http-client https://packagist.org/packages/react/http-client

ReactPHP── PHP版的Node.js https://www.csdn.net/article/2015-10-12/2825887

ReactPHP 爬虫实战:下载整个网站的图片 https://learnku.com/laravel/t/17492

https://sergeyzhuk.me/2018/02/12/fast-webscraping-with-reactphp/

https://sergeyzhuk.me/2018/08/31/fast-webscraping-images/

clue/block-react https://packagist.org/packages/clue/block-react


返回