Heim >Backend-Entwicklung >PHP-Tutorial >Implementierung eines Servers mit hoher Parallelität mit PHP
Wenn es um hohe Parallelität geht, gibt es keine Möglichkeit, das I/O-Multiplexing zu umgehen und dann die spezifische Plattform anzugeben Unter Linux gibt es keine Möglichkeit, Epoll zu umgehen. Ich werde nicht auf das Prinzip eingehen, warum Epoll effizient ist.
Wie spiele ich Epoll mit PHP? Zuerst müssen Sie die Libevent-Bibliothek installieren und dann die Event-Erweiterung oder Libevent-Erweiterung installieren, und Sie können problemlos spielen.
Einige Leute sind verwirrt über den Unterschied zwischen der Libevent-Bibliothek und der Libevent-Erweiterung. Einfach ausgedrückt ist die Libevent-Bibliothek die Kapselung von Epoll in der C-Sprache und hat nichts mit PHP zu tun. Die Libevent-Erweiterung ist die Kommunikationsbrücke zwischen PHP und der Libevent-Bibliothek. Tatsächlich tun dies viele Erweiterungen von PHP. Es gibt einige hervorragende C-Sprachbibliotheken. Wenn PHP diese direkt verwenden möchte, kann es über PHP-Erweiterungen mit PHP verbunden werden.
Wählen Sie entweder die Libevent-Erweiterung oder die Event-Erweiterung. Ich persönlich bevorzuge die Event-Erweiterung, weil sie objektorientierter ist. Gehen Sie zu http://pecl.php.net und suchen Sie nach Erweiterungen, die Ihrer PHP-Version entsprechen, laden Sie sie herunter, kompilieren und installieren Sie sie. Wenn Sie mit mehreren PHP-Versionen auf Ihrem Computer kompilieren, achten Sie auf Folgendes Machen Sie keinen Fehler, die typischen fünf Schritte:
phpize ./configure make make install php -m | grep event #看看装上了没
Die Transportschicht des Servers, die wir implementieren möchten, ist das TCP-Protokoll. Es gibt zu viele Protokolle der Anwendungsschicht Aus Platzgründen verwenden wir lediglich den HTTP-Server als Beispiel. Das HTTP-Protokoll selbst ist sehr komplex. Es sind viele Details zu implementieren, und wir werden das HTTP-Protokoll nicht vollständig implementieren.
Erstellen Sie zunächst einen Socket, drei Schritte: socket_create, socket_bind, socket_listen. Warum diese drei Schritte? Es ist ganz einfach: Unabhängig von Ihrem Transportschichtprotokoll müssen Sie eine Version des folgenden Netzwerkschichtprotokolls auswählen: IPV4 oder IPV6. Sie müssen eine Transportschicht-Arbeitsmethode wählen, Vollduplex, Halbduplex oder Simplex, TCP. Oder UDP, Sie müssen eine auswählen, socket_create hat diese drei Optionen; nachdem Sie die Netzwerkschicht und die Transportschicht bestimmt haben, müssen Sie mir mitteilen, welcher Port überwacht werden soll, was socket_bind entspricht, und dann müssen Sie die Überwachung aktivieren und eine Client-Warteschlange angeben Länge, das ist es, was socket_listen tut.
Nach der Erstellung führen wir keine synchrone Blockierung ein. Wenn Sie mehr Verbindungen gleichzeitig anfordern, müssen Sie warten, bis die durch socket_listen angegebene Warteschlangenlänge erreicht ist überschritten wird, wird 504 zurückgegeben. Das Gleiche gilt für mehrere Prozesse, bei denen mehrere Prozesse gleichzeitig ablaufen. Der Kontextwechsel von Prozessen ist zeitaufwändig und mühsam, was zu einer Ineffizienz des gesamten Systems führt.
Es spielt keine Rolle, wir haben Epoll, es ist kein Traum, Tausende von Anfragen zu speichern, sondern zuerst einen Reaktor zu implementieren. Die libevent-Bibliothek ist der Reactor-Modus. Der direkte Aufruf der Funktion erfolgt über den Reactor-Modus, sodass Sie sich keine Gedanken darüber machen müssen, wie der Reactor-Modus in PHP implementiert wird.
<?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(); } }
Der obige Code ist sehr einfach. EventBase ist ein Container, der Event-Instanzen enthält. Dann erstellt ein 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 = ''; protected $write_buffer = ''; 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"); } } }
zunächst einen dreistufigen Socket und versetzt ihn in den nicht blockierenden Modus. Fügen Sie dann den Socket zum Reaktor hinzu, um auf lesbare Ereignisse zu warten. Lesbar bedeutet, dass er nur gelesen werden kann, wenn sich Daten im Puffer befinden. Es tritt ein lesbares Ereignis auf, das darauf hinweist, dass eine neue Verbindung ansteht. Verwenden Sie stream_socket_accept
, um die neue Verbindung Conn zu empfangen, und setzen Sie Conn in den Reactor, um auf lesbare Ereignisse zu warten. Dies zeigt an, dass der Client Daten gesendet hat in einer Schleife, bis keine Daten mehr vorhanden sind, und setzen Sie dann Conn in Reactor, um auf beschreibbare Ereignisse zu warten. Das bedeutet, dass die Client-Daten gesendet wurden und die Antwort geschrieben wurde.
Wenn es sich bei der Anwendungsschicht um ein HTTP-Protokoll handelt, achten Sie bitte auf den Keep-Alive-Header Connection: Da die Verbindung wiederverwendet werden muss, schließen Sie die Verbindung nicht, sobald sie beendet ist.
Nach Abschluss der Arbeit verwenden Sie ab
, um die Parallelität zu testen und -k
Parameter-Wiederverwendungsverbindung hinzuzufügen. Es gibt kein Problem mit i5+8G, 3W-Parallelität. Natürlich haben wir keine Festplatte Die tatsächliche Situation ist, dass das Lesen von Dateien von der Festplatte Linux-Systemaufrufe erfordert, und es gibt mehrere Dateikopiervorgänge, die relativ teuer sind. Die übliche Lösung ist das direkte Kopieren von Dateien von einem FD zu einem anderen effizienter, der Nachteil ist, dass PHP keine vorgefertigten Sendfile-Erweiterungen hat, sodass Sie es selbst tun müssen und die Entwicklungskosten etwas hoch sind.
AB-Test PO-Bild:
Dies ist die Idee, einen Server mit hoher Parallelität in PHP zu implementieren. Solange es mit EPOLL gelöst wird, ist es ein dreistufiger Prozess, der überwacht werden soll FD-Veranstaltungen. Natürlich ist dies nur das einfachste Modell, und es gibt viele Bereiche, die verbessert werden können, zum Beispiel Multiprozess, Kopieren von Nginx, ein Hauptprozess + N Worker-Prozesse, der Zweck von Multiprozess ist die Verwendung von Multicore parallel zu arbeiten. Das Gleiche gilt für die C-Sprachimplementierung, aber Sie dürfen die Libevent-Bibliothek nicht selbst verwenden und EPOLL kapseln. Schließlich ist die Libevent-Bibliothek etwas umfangreich und Sie können natürlich nicht viele Dinge in der C-Sprache verwenden In den Datenstrukturen sind eine Reihe von Datenstrukturen und Funktionen definiert. Die Operation muss geschrieben werden, es gibt keinen GC, der Speicher muss von Ihnen selbst verwaltet werden und es muss ein gutes Design vorhanden sein Bei der IPC-Interprozesskommunikation ist die Entwicklungsschwierigkeit viel größer als bei PHP, und der Entwicklungszyklus ist auch sehr lang. Wenn Sie interessiert sind, können Schüler selbst damit spielen.
Weitere technische Artikel zum Thema PHP finden Sie in der Spalte PHP-Tutorial zum Lernen!
Das obige ist der detaillierte Inhalt vonImplementierung eines Servers mit hoher Parallelität mit PHP. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!