Home > Article > Backend Development > Implementing high-concurrency server with PHP
When it comes to high concurrency, there is no way to bypass I/O multiplexing and then specify the specific platform Linux, there is no way to bypass epoll. I won’t go into the principle of why epoll is efficient. Interested students can search and study it by themselves.
How to play epoll in php? First, you must install a libevent library, and then install an event extension or libevent extension, and you can play happily.
Some people are confused about the difference between the libevent library and the libevent extension. Simply put, the libevent library It is the encapsulation of epoll in C language and has nothing to do with PHP; the libevent extension is the communication bridge between PHP and the libevent library. In fact, many extensions of PHP do this. There are some excellent C language libraries. If PHP wants to use them directly, it can be connected to PHP through PHP extensions.
Choose between libevent extension and event extension. Personally, I prefer event extension because it is more object-oriented. Go to http://pecl.php.net and search for extensions corresponding to your PHP version, download it, compile and install it and it will be OK. When compiling with multiple versions of PHP installed on your computer, pay attention to the version of phpize that matches the one. , make no mistake, the typical five steps:
phpize ./configure make make install php -m | grep event #看看装上了没
The transport layer of the server we want to implement is the TCP protocol. There are too many application layer protocols and are too complicated. Due to space limitations, we will simply use the HTTP server as an example. For example, the HTTP protocol itself is very complex. There are many details to implement, and we will not fully implement the HTTP protocol.
First, create a socket, three steps, socket_create, socket_bind, socket_listen. Why these three steps? It's very simple. No matter what your transport layer protocol is, you have to choose a version of the network layer protocol below, IPV4 or IPV6. You have to choose a transport layer working method, full duplex, half duplex or simplex, TCP. Or UDP, you have to choose one, socket_create is these three options; after determining the network layer and transport layer, you have to tell me which port to listen to, which corresponds to socket_bind; then you have to enable monitoring and specify a client Queue length, this is what socket_listen does.
After creation, we will not introduce synchronous blocking. A process can hold at most one connection at the same time. If more connections are requested at the same time, you have to wait. If the queue length specified by socket_listen is exceeded, 504 must be returned. . The same goes for multiple processes. Several processes have several concurrent processes. Processes are expensive resources, and the context switching of processes is time-consuming and laborious, resulting in inefficiency of the entire system.
It doesn't matter, we have epoll, it is not a dream to hold thousands of requests, first implement a Reactor. The libevent library is the Reactor mode. Calling the function directly is using the Reactor mode, so there is no need to worry about how to implement the Reactor mode in PHP.
<?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(); } }
The above code is very simple. Let me briefly explain the concept. EventBase is a container, which contains Event instances. In this way, the above code is very easy to understand. Then a 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"); } } }
first creates a Socket in three steps and sets it to non-blocking mode. Then add the socket to the Reactor to listen for readable events. Readable means that it can only be read when there is data in the buffer. A readable event occurs, indicating that a new connection is coming. Use stream_socket_accept
to receive the new connection Conn, and put Conn in Reactor to monitor readable events. If a readable event occurs, it indicates that the client has data sent. Read in a loop until there is no data, and then put Conn in the Reactor to listen for writable events. If a writable event occurs, it means that the client data has been sent. Assemble the protocol and write the response.
If the application layer is HTTP protocol, please pay attention to the Connection: keep-alive header, because the connection needs to be reused, so do not close the connection as soon as it is finished.
After finishing the work, use ab
to test the concurrency, and add the -k
parameter to multiplex the connection. There is no problem with i5 8G, 3W concurrency, of course we don’t have it here. Disk I/O, the actual situation is to read files from the disk, and read files through Linux system calls, and there are several file copy operations, which is relatively expensive. The common solution is sendfile, zero copy directly from an FD To another FD, the efficiency is relatively high. The disadvantage is that PHP does not have a ready-to-implement sendfile extension, so you have to do it yourself, and the development cost is a bit high.
ab test PO picture:
This is the idea of implementing a high-concurrency server in PHP. As long as it is solved with EPOLL, the idea is the same. It is a three-step process. Put it under Reactor to monitor FD events. Of course, this is just the simplest model, and there are many areas that can be improved. For example, multi-process, copy nginx, one main process and N worker processes. The purpose of multi-process is to use multi-core parallel work. The same is true for C language implementation, but you may not use the libevent library and encapsulate EPOLL yourself. After all, the libevent library is a bit heavy, and you can’t use many things in libevent; of course, the C language has a bunch of data structures and functions defined on the data structures. The operations need to be written, there is no GC, the memory must be managed by yourself, and there must be good design. When running multiple processes, you need to work on IPC inter-process communication. The development difficulty is much greater than that of PHP, and the development cycle is also very long. If you are interested Students can play with one by themselves.
For more PHP related technical articles, please visit the PHP Tutorial column to learn!
The above is the detailed content of Implementing high-concurrency server with PHP. For more information, please follow other related articles on the PHP Chinese website!