This publication does not pretend to complete the solution of the question. The server is developed for informational purposes only. Many important issues, such as, for example, error handling of sockets, are omitted. To implement a multi-threaded server, we will use, of course, threads. Very often you have to see the phrase that, they say, there are no threads in PHP. So this is not true. There are threads, but they are implemented in a separate
pthreads extension.
First we need to build PHP compiled with thread safety flag. I use Windows for work, so I downloaded the finished package
here . You just need to choose the OS bit, the correct PHP version and, of course, the Thread Safe version. During the article it will be assumed that we have unpacked the archive with PHP into the C: \ php directory. Next, we need to install the pthreads extension. Go
here and select the version corresponding to the downloaded version of PHP and system capacity. From the archive, copy the file php_pthreads.dll to the directory C: \ php \ ext and the file pthreadVC2.dll in the directory C: \ php and C: \ Windows \ System32. In the C: \ php directory, rename the php.ini-development file in php.ini and add the following line to it:
extension=php_pthreads.dll
We also find and unravel the extension_dir directive and set it to the value “C: \ php \ ext” (in my version of PHP7, relative paths did not work). Open the command prompt and check:
C:\php\php.exe -v
At the end of the first line of output, we should see a mark (ZTS). We proceed directly to the implementation of the server. Create a file (in my case it will be located at C: \ server.php. First, create a socket that will listen to 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, create a pool of workers.
$pool = new Pool(10, Worker::class);
The first argument sets the maximum number of valid threads, the second class name of the worker. For any more specific tasks, you can describe your class, inheriting it from the class Worker. We will use the original class. Looking ahead, I’ll say that the installed class in the thread class can be obtained through $ this-> worker.
Next, we will implement a 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));
Our class accepts a socket connection to the client in the constructor. Also, actions performed in a stream must be described in the run () method. In my case, this is the response of the client to the basic headers and “Hello world!” Text.
Next, we will cyclically try to accept connections from the client, and, if successful, create a separate thread 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 are using an infinite loop, I will register a function that will be executed when the script exits 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.
<?php $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_bind($server, '127.0.0.1', 8080); socket_listen($server); $pool = new Pool(10, Worker::class); 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)); } } } 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!