Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

When it comes to high concurrency, there is no way to bypass I/O multiplexing, and when it comes to specific platforms like Linux, there is no way to bypass epoll. The reason why epoll is efficient is not mentioned, but if you are interested in epoll, you can search for it or use Swoole, which is quite good.

How does PHP play epoll? First you have to install a libevent library. Then you have to install an Event or libevent extension and you can have fun.

Some people don’t know the difference between the Libevent library and the Libevent extension. Simply put, the Libevent library is a WRAPPER for Epoll that has nothing to do with PHP.

The Libevent extension is the bridge between PHP and the Libevent library. In fact, there are many PHP extensions that do just that. There are some excellent C libraries that PHP wants to use directly, just plug into PHP as a PHP extension.

Install either the Libevent extension or the Event extension. The event extension is more object-oriented. Search pecl.php.net for an extension that corresponds to your version of PHP. If you have multiple versions of PHP installed on your computer, make sure that the version of PHPize matches it. Make no mistake, there are five typical steps:

Phpize. / configure make make install PHP -m | grep event # look at mountCopy the code

The server we want to implement, the transport layer is TCP protocol, the application layer protocol is too many and too complex, limited space, I will simply take HTTP server as an example, HTTP protocol itself is very complex, there are a lot of details to implement, we will not fully implement HTTP protocol.

First, create a socket in three steps: socket_create, socket_bind, socket_listen.

Whether you want to use IPV4 or IPV6, whether you want to use the transport layer protocol, whether you want to use full-duplex, half-duplex, or simplex, or TCP or UDP, socket_create is the three options.

So once you’ve identified the network layer and transport layer, you need to tell me which port to listen on, and that corresponds to socket_bind; Then you have to turn on the listener and specify a client queue length, which is what socket_listen does.

Socket_listen () {socket_listen () {socket_listen () {socket_listen () {socket_listen () {socket_listen () {socket_LISTEN () {socket_LISTEN () {socket_LISTEN (); Multi-process is the same, several processes have several concurrent, process is expensive resources, and process context switch is time-consuming and laborious, resulting in low efficiency of the whole system.

It doesn’t matter, we have epoll, holding thousands of requests is not a dream, first realize a Reactor. The Libevent library is the Reactor schema. Calling functions directly uses the Reactor schema, so there is no need to worry about how PHP implements the Reactor schema.


      
 
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(a);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('Non-existent event');
            }
 
            $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(); }}Copy the code

EventBase is a container that holds an Event instance. And then a Server.


       
 
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


      
 
class 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"); }}}Copy the code

Create three steps of the Socket and set it to non-blocking mode. Then add the socket to the Reactor to listen for readable events. Readable means that the buffer is readable only when there is data in it. Stream_socket_accept accepts Conn as a new connection, and places Conn in the Reactor to listen for a readable event. If a readable event occurs, the client sends data, and loops until there is no more data. Conn is then placed in the Reactor to listen for writable events. When writable events occur, the client is finished sending data, and the protocol is assembled to write the response.

Note the Connection: keep-alive header if the application layer is HTTP, because the Connection needs to be reused, do not close the Connection as soon as it is written.

I5 +8G, 3W concurrency no problem, of course we do not have disk I/O here, the actual situation is to read files from disk, read files through Linux system call, and there are several file copy operations, the cost is relatively large, The common solution is sendfile, zero copy directly from one FD to another FD, relatively high efficiency, the disadvantage is that PHP does not have a ready-made extension to implement Sendfile, you have to do it yourself, the development cost is a bit high.

This is how PHP implements a high concurrency server. As long as EPOLL is used to solve the problem, the idea is the same. It is a three-step process, which is placed under Reactor to listen for FD events. Of course, this is only the simplest model, there are a lot of improvements, such as multi-process, copy nginx, a main process +N worker process, the purpose of multi-process is still to use multi-core parallel work.

The C implementation is the same, but you might not need the Libevent library and wrap EPOLL yourself. After all, the Libevent library is a bit heavy and you won’t need much of libevent. And, of course, there is a pile of data structure and C defined on the data structure to write operation, no GC, memory management, but also has the good design, multiple processes have to make a figure on the IPC inter-process communication, development difficulty is the earth more than PHP, development cycle is very long, students interested in a play can be rolled.