PHP multithreaded server implementation

This publication does not claim to be a complete solution to the question. The server is developed solely for educational purposes. Many important issues, such as, for example, handling socket errors, are omitted. To implement a multi-threaded server, we will, of course, use threads. Very often you have to see the phrase that, they say, in PHP there are no threads. So this is not true. There are threads, but they are implemented in a separate pthreads extension .

First, we need the PHP assembly compiled with the thread safety flag. I use Windows to work, so I downloaded the finished package here. You just need to choose the right bit depth of the OS, the desired version of PHP and, of course, Thread Safe version. Throughout the article it will be assumed that we unpacked the archive with PHP into the C: \ php directory. Next we need to install the pthreads extension. We go here and select the version corresponding to the downloaded version of PHP and the capacity of the system. From the archive, copy the php_pthreads.dll file to the C: \ php \ ext directory and the pthreadVC2.dll file to the C: \ php and C: \ Windows \ System32 directories. In the C: \ php directory, rename the php.ini-development file to php.ini and add the following line to it:

extension=php_pthreads.dll

We also find and unzip the extension_dir directive and set it to “C: \ php \ ext” (in my version of PHP7, relative paths did not work). Open the command line and check:

C:\php\php.exe -v

At the end of the first line of output, we should see a mark (ZTS). We pass directly to the server implementation. We create a file (in my case, it will be located at C: \ server.php. First, create a socket that will listen on port 8080 on our local machine.

$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($server, '127.0.0.1', 8080);
socket_listen($server);

Next, we create a pool of workers.

$pool = new Pool(10, Worker::class);

The first argument sets the maximum number of valid threads, the second is the name of the worker class. For any more specific tasks, you can describe your class by inheriting it from the Worker class. We will use the original class. Looking ahead, I’ll say that in a thread class, an installed worker can be obtained through $ this-> worker.

Next, we implement the class that will be executed in a separate thread. The class must inherit from Threaded.

class Task extends Threaded
{
    protected $socket;
    public function __construct($socket)
    {
        $this->socket = $socket;
    }
    public function run()
    {
        if (!empty($this->socket)) {
            $response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 12\r\n\r\nHello world!";
            socket_write($this->socket, $response, strlen($response));
            // при попытке закрытия сокета я получаю ошибку zend_mm_heap currupted, поэтому эту часть в тестовом решении опускаю 
            //socket_close($this->socket);
        }
    }
}

Our class accepts a client connection socket in the constructor. Also, the actions performed in the stream should be described in the run () method. In my case, this is the answer to the client with the base headers and the text “Hello world!”.

Next, we will cyclically try to accept connections from the client, and, if successful, create a separate stream and pass the socket descriptor there.


$servers = [$server];
while (true) {
    $read = $servers;
    if (socket_select($read, $write, $except, 0) >= 0 && in_array($server, $read)) {
        $task = new Task(socket_accept($server));
        $pool->submit($task);
    }
}

Since we use an infinite loop, I will register a function that will execute when the script completes and stops the pool. The function should be registered before the start of the cycle.


register_shutdown_function(function () use ($server, $pool) {
    if (!empty($server)) {
        socket_close($server);
    }
    $pool->shutdown();
});

Actually everything. We start the server at the command line and try to open localhost : 8080 in the browser .

cd C:\
C:\php\php.exe server.php

Below is the full server code.


socket = $socket;
    }
    public function run()
    {
        if (!empty($this->socket)) {
            $response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 12\r\n\r\nHello world!";
            socket_write($this->socket, $response, strlen($response));
        }
    }
}
register_shutdown_function(function () use ($server, $pool) {
    if (!empty($server)) {
        socket_close($server);
    }
    $pool->shutdown();
});
$servers = [$server];
while (true) {
    $read = $servers;
    if (socket_select($read, $write, $except, 0) >= 0 && in_array($server, $read)) {
        $task = new Task(socket_accept($server));
        $pool->submit($task);
    }
}

Thanks for attention!

Also popular now: