引言
ReactPHP是事件驱动的,非阻塞I/O的PHP,它和Node.js都是采用单进程和单线程的方式, 适用于I/O密集型,不适用于计算密集型。
React下有好多库,比较常用的有:
react/event-loop
-ReactPHP的核心响应事件循环,这个库可用于事件I/Oreact/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)的鼓舞。
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爬虫
看一下下面的异步实现:
但是保存图片时,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