PHP 的 Web 運行原理 ( 3 ) - Reactor 的實現之 reactPHP
php
Lastmod: 2019-12-16

上一篇文章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 模式的程式碼,那麼它的運行原理流程會如下 :

  1. 建立 event loop。
  2. 將 addPeriodicTimer 5 秒後要執行的事件丟到 queue 中。
  3. 執行 event loop。(也就是開始跑無窮迴圈)
  4. event loop 在 5 秒時,發現有個要執行的任務 callback function
  5. 執行 echo “Hi Mark”。
  6. 將立 http 請求,並將它丟到 queue 中。
  7. event loop 當發現 I/O 操作有回復時,則執行 callback function
  8. 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 操作,就如同範例一樣。

參考資料

comments powered by Disqus