ホームページ  >  記事  >  バックエンド開発  >  PHP を使用した高同時実行サーバーの実装

PHP を使用した高同時実行サーバーの実装

步履不停
步履不停オリジナル
2019-06-19 10:55:234021ブラウズ

PHP を使用した高同時実行サーバーの実装

#高い同時実行性に関しては、I/O 多重化をバイパスして特定のプラットフォームを指定する方法はありません。 Linux では、epoll をバイパスする方法はありません。epoll が効率的である理由については説明しません。興味のある学生は、自分で検索して学習してください。

PHP で epoll を再生するにはどうすればよいですか?まず、libevent ライブラリをインストールし、次にイベント拡張機能または libevent 拡張機能をインストールする必要があります。そうすれば楽しくプレイできます。

libevent ライブラリと libevent 拡張機能の違いについて混乱している人もいます。簡単に言うと、 libevent ライブラリ これは C 言語での epoll のカプセル化であり、PHP とは何の関係もありません; libevent 拡張機能は、PHP と libevent ライブラリの間の通信ブリッジです。実際、PHP の多くの拡張機能がこれを行います。優れた C 言語ライブラリがいくつかあり、PHP がそれらを直接使用したい場合は、PHP 拡張機能を介して PHP に接続できます。

libevent 拡張機能とイベント拡張機能のどちらかを選択します。個人的には、よりオブジェクト指向であるため、イベント拡張機能を好みます。 http://pecl.php.net にアクセスして、お使いの PHP バージョンに対応する拡張機能を検索し、ダウンロードし、コンパイルしてインストールすれば問題ありません。コンピューターにインストールされている複数のバージョンの PHP でコンパイルする場合は、次の点に注意してください。典型的な 5 つの手順は間違いありません:

phpize
./configure
make
make install
php -m | grep event #看看装上了没
実装したいサーバーのトランスポート層は TCP プロトコルです。アプリケーション層プロトコルが多すぎます。複雑です。スペースの制限のため、例として HTTP サーバーを簡単に使用します。たとえば、HTTP プロトコル自体は非常に複雑です。実装する詳細が多数あるため、HTTP プロトコルを完全には実装しません。

まず、ソケットを作成します。socket_create、socket_bind、socket_listen の 3 つの手順を実行します。なぜこの 3 つの手順を実行するのでしょうか?それはとても簡単です。トランスポート層プロトコルが何であっても、以下のネットワーク層プロトコルのバージョン (IPV4 または IPV6) を選択する必要があります。トランスポート層の動作方法 (全二重、半二重、またはシンプレックス、TCP) を選択する必要があります。または UDP、どちらかを選択する必要があります。socket_create はこれら 3 つのオプションです。ネットワーク層とトランスポート層を決定した後、socket_bind に対応するどのポートをリッスンするかを指定する必要があります。その後、モニタリングを有効にしてクライアントを指定する必要があります。キューの長さ、これがsocket_listenの機能です。

作成後は、同期ブロッキングは導入しません。プロセスは同時に最大 1 つの接続を保持できます。同時により多くの接続が要求された場合は、待機する必要があります。キューの長さが指定された場合、 .socket_listen を超過したため、504 を返す必要があります。複数のプロセスについても同様で、複数のプロセスが同時に実行されるため、プロセスは高価なリソースであり、プロセスのコンテキスト切り替えには時間と手間がかかり、システム全体の効率が低下します。

それは問題ではありません。私たちは epoll を持っています。何千ものリクエストを保持することも夢ではありません。まず Reactor を実装してください。 libevent ライブラリは Reactor モードであり、関数を直接呼び出すことは Reactor モードを使用するため、PHP で Reactor モードを実装する方法を気にする必要はありません。

<?php

use Event;
use EventBase;

class Reactor
{
    protected $reactor;

    protected $events;

    public static $instance = null;

    const READ = Event::READ | Event::PERSIST;

    const WRITE = Event::WRITE | Event::PERSIST;

    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
            self::$instance->reactor = new EventBase;
        }

        return self::$instance;
    }

    public function add($fd, $what, $cb, $arg = null)
    {
        switch ($what) {
            case self::READ:
                $event = new Event($this->reactor, $fd, self::READ, $cb, $arg);
                break;
            case self::WRITE:
                $event = new Event($this->reactor, $fd, self::WRITE, $cb, $arg);
                break;
            default:
                $event = new Event($this->reactor, $fd, $what, $cb, $arg);
                break;
        }

        $event->add();
        $this->events[(int) $fd][$what] = $event;
    }

    public function del($fd, $what = 'all')
    {
        $events = $this->events[(int) $fd];
        if ($what == 'all') {
            foreach ($events as $event) {
                $event->free();
            }
        } else {
            if ($what != self::READ && $what != self::WRITE) {
                throw new \Exception('不存在的事件');
            }

            $events[$what]->free();
        }
    }

    public function run()
    {
        $this->reactor->loop();
    }

    public function stop()
    {
        foreach ($this->events as $events) {
            foreach ($events as $event) {
                $event->free();
            }
        }
        $this->reactor->stop();
    }
}
上記のコードは非常にシンプルです。概念を簡単に説明します。EventBase は Event インスタンスを含むコンテナーです。このように、上記のコードは非常に理解しやすいです。次に、Server.

<?php use Throwable;use Monolog\Handler\StreamHandler;class Server{	protected $ip;	protected $port;	protected $socket;	protected $reactor;	public function __construct($ip, $port)
	{		$this->ip = $ip;		$this->port = $port;
	}	public function start()
	{
	    $socket = $this->createTcpConnection();
	    stream_set_blocking($socket, false);

	    Reactor::getInstance()->add($socket, Reactor::READ, function($socket) {
                $conn = stream_socket_accept($socket);
                stream_set_blocking($conn, false);
                (new Connection($conn))->handle();
        });

            Reactor::getInstance()->run();
	}	public function createTcpConnection()
	{
		$schema = sprintf("tcp://%s:%d", $this->ip, $this->port);
		$socket = stream_socket_server($schema, $errno, $errstr);		if ($errno) {			throw new \Exception($errstr);
		}		return $socket;
	}
}
Connection

<?phpclass Connection{    protected $conn;    protected $read_buffer = &#39;&#39;;    protected $write_buffer = &#39;&#39;;    public function __construct($conn)
    {        $this->conn = $conn;
    }    public function handle()
    {
        Reactor::getInstance()->add($this->conn, Reactor::READ, \Closure::fromCallable([$this, 'read']));
    }    private function read($conn)
    {        $this->read_buffer = '';        if (is_resource($conn)) {            while ($content = fread($conn, 65535)) {                $this->read_buffer .= $content;
            }
        }        if ($this->read_buffer) {
            Reactor::getInstance()->add($conn, Reactor::WRITE, \Closure::fromCallable([$this, 'write']));
        } else {
            Reactor::getInstance()->del($conn);
            fclose($conn);
        }
    }    private function write($conn)
    {        if (is_resource($conn)) {
            fwrite($conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=utf8\r\nContent-Length:11\r\nConnection: keep-alive\r\n\r\nHello!world");
        }
    }

}
は、まず 3 つのステップでソケットを作成し、それをノンブロッキング モードに設定します。次に、ソケットを Reactor に追加して、読み取り可能なイベントをリッスンします。読み取り可能とは、バッファーにデータがある場合にのみ読み取ることができることを意味します。読み取り可能なイベントが発生し、新しい接続が来ていることを示します。

stream_socket_accept を使用して新しい接続 Conn を受信し、Conn を Reactor に入れて読み取り可能なイベントを監視します。読み取り可能なイベントが発生した場合は、クライアントがデータが送信されます。データがなくなるまでループで読み取り、Reactor に Conn を入れて書き込み可能なイベントをリッスンします。書き込み可能なイベントが発生した場合、クライアント データが送信されたことを意味します。プロトコルを組み立てて応答を書き込みます。

アプリケーション層が HTTP プロトコルの場合は、Connection: keep-alive ヘッダーに注意してください。接続は再利用する必要があるため、接続が終了したらすぐに閉じないでください。

作業が完了したら、

ab を使用して同時実行性をテストし、-k パラメーターを追加して接続を多重化します。i5 8G、3W では問題ありません同時実行性 (もちろん、ここにはありません。ディスク I/O、実際の状況では、ディスクからファイルを読み取り、Linux システム コールを介してファイルを読み取ります。また、いくつかのファイル コピー操作があり、比較的コストがかかります)。一般的な解決策は sendfile です。FD から直接別の FD にコピーする必要はありません。効率は比較的高いです。欠点は、PHP にはすぐに実装できる sendfile 拡張機能がないため、自分で行う必要があり、開発コストがかかることです。少し高いです。

ab テスト PO 写真:

PHP を使用した高同時実行サーバーの実装

これは、PHP で高同時実行サーバーを実装するというアイデアです。EPOLL で解決する限り、アイデアは同じです。これは 3 段階のプロセスです。それを Reactor の下に置いて監視します。 FDイベント。もちろん、これは最も単純なモデルであり、マルチプロセス、コピー nginx、1 つのメインプロセスと N 個のワーカープロセスなど、改善できる点はたくさんあります。マルチプロセスの目的は、マルチコアを使用することです。並行作業。同じことが C 言語の実装にも当てはまりますが、libevent ライブラリを使用して EPOLL を自分でカプセル化することはできません。結局のところ、libevent ライブラリは少し重いため、libevent では多くの機能が使用できません。もちろん C 言語は必要ありません。大量のデータ構造とそのデータ構造上で定義された関数がある オペレーションを記述する必要がある、GC がない、メモリを自分で管理する必要がある、適切な設計が必要である 複数のプロセスを実行する場合、作業が必要IPCのプロセス間通信については、PHPに比べて開発難易度が高く、開発サイクルも非常に長いので、興味があれば学生一人でも遊んでみてはいかがでしょうか。

PHP 関連の技術記事の詳細については、PHP チュートリアル 列にアクセスして学習してください。

以上がPHP を使用した高同時実行サーバーの実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。