上一篇文章PHP 的 Web 運行原理 ( 2 ) - 非阻塞 I/O 之 Reactor 模式我們理解到實現非阻塞 I/O 的 reactor 模式以後,接下來本篇文章我們將來要說明,在 php 中的 reactor 實現reactPHP
。
本篇文章分為以下三個章節 :
- reactPHP 基本概念
- reactPHP 非阻塞 I/O 實現
- reactPHP 使用時注意事項
reactPHP 基本概念
reactPHP 官網寫這一段話 :
Event-driven, non-blocking I/O with PHP
它是一個用 php 所寫的 libaray,可以幫助我們做以下的事情 :
- 可以建立一個非阻塞 I/O 的網路服務。
- 可以建立一個定時排程服務。
http server 的範例
下面就是官網首頁的範例,我們可以用它簡單的建立一個非阻塞 I/O 的 http server,就如同 nodejs 一樣。
$loop = React\EventLoop\Factory::create();
$server = new React\Http\Server(function (Psr\Http\Message\ServerRequestInterface $request) {
return new React\Http\Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
$socket = new React\Socket\Server(8080, $loop);
$server->listen($socket);
echo "Server running at http://127.0.0.1:8080\n";
$loop->run();
排程服務的範例
$loop = React\EventLoop\Factory::create();
// 5 秒後執行
$loop->addPeriodicTimer(5, function () {
echo "Hi Mark";
});
$loop->run();
reactPHP 非阻塞 I/O 實現
那 reactPHP 它是如何實現非阻塞 I/O 模式呢 ?
它就是如我們標題所說,它使用reactor 模式
來實現非阻塞 I/O 模式,並且它與 nodejs 一樣使用 single process 的 reactor 模式。
備註: 為什麼 single process 可以做到非阻塞 I/O 請參考前一篇文章。
架構圖如下,就是一個 reactor 機制,其中它的 process 裡面就有所謂的 I/O 多路復用技術。
而我們實際上的運行機制如下圖:
接這我們來看看,如果是段 reactor 模式的程式碼,那麼它的運行原理流程會如下 :
- 建立 event loop。
- 將 addPeriodicTimer 5 秒後要執行的事件丟到 queue 中。
- 執行 event loop。(也就是開始跑無窮迴圈)
- event loop 在 5 秒時,發現有個要執行的任務 callback function
- 執行 echo “Hi Mark”。
- 將立 http 請求,並將它丟到 queue 中。
- event loop 當發現 I/O 操作有回復時,則執行 callback function
- echo $chunk。
(1) $loop = React\EventLoop\Factory::create();
$client = new React\HttpClient\Client($loop);
// 5 秒後執行
(2) $loop->addPeriodicTimer(5, function () {
(4)
(5) echo "Hi Mark";
(6) $request = $client->request('GET', 'https://github.com/');
$request->on('response', function ($response) {
$response->on('data', function ($chunk) {
(7) echo $chunk;
});
$response->on('end', function() {
echo 'DONE';
});
});
(3) $loop->run();
使用時注意事項
reactPHP 理論上是 php 版的 nodejs 但實際使用要注意,不是。
假設我們使用 reactPHP 然後來寫一個 http server。
然後當 server 收到一個請求時,它會再使用file_get_contents
這個方法去打某個 api,這 api 需要一分鐘左右才能回應。
<?php
require __DIR__ . '/vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$server = new React\Http\Server(function (Psr\Http\Message\ServerRequestInterface $request) use ($client) {
// ----------------------------------------------------
var_dump('wait...');
// 假設這一段要執行一分鐘。
$response = file_get_contents('http://127.0.0.1:3000');
var_dump('done !...');
// ----------------------------------------------------
return new React\Http\Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
$socket = new React\Socket\Server(8080, $loop);
$server->listen($socket);
echo "Server running at http://127.0.0.1:8080\n";
$loop->run();
假設往這個使用 reactPHP 寫的 http server,發送兩次請求,上面的 wait 會看到幾次呢 ?
答案是 : 1 次
因為第一次請求時,就將整個 process 給阻塞住了,所以第二個請求完全無法處理。
它不是說它有提供非阻塞 I/O 的功能嗎 ?
嗯對的,它有提供,但是前提假設為,你要使用它。
你在寫 nodejs 時,上面這個相似範例 process 就不會被阻塞住,因為它在給 v8 處理完 binding c++ 階段時,會將所有的 I/O 操作執行完並且丟到 event loop 機制中,所以在 nodejs 操作 I/O 時,你什麼都沒做,就會是非阻塞的 I/O 操作。
而在 reactPHP 中,你要自已處理又或是使用它所提供的 http client 才能讓變成非阻塞 I/O 的操作。如下範例。
<?php
require __DIR__ . '/vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$client = new React\HttpClient\Client($loop);
$server = new React\Http\Server(function (Psr\Http\Message\ServerRequestInterface $request) use ($client) {
// ----------------------------------------------------
var_dump('wait...');
$request = $client->request('GET', 'http://127.0.0.1:3000');
$request->on('response', function ($response) {
$response->on('data', function ($chunk) {
echo $chunk;
});
$response->on('end', function() {
echo 'DONE';
});
var_dump('done !...');
// ----------------------------------------------------
return new React\Http\Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
$socket = new React\Socket\Server(8080, $loop);
$server->listen($socket);
echo "Server running at http://127.0.0.1:8080\n";
$loop->run();
結論
本篇文章中我們學習了幾個重點:
reactPHP 基本概念
就是這段話。
Event-driven, non-blocking I/O with PHP
reactPHP 非阻塞 I/O 實現
它使用single process 的 reactor 模式
來實現。
使用時注意事項
在使用它時注意,你要確保你所有的 I/O 操作,都有丟到 event loop 中,否則,它仍然是阻塞 I/O 操作,就如同範例一樣。