📜 ⬆️ ⬇️

We make web sites for PHP from scratch. Part 2. IPC

After writing my previous article Making web sockets for PHP from scratch, I realized that the community has some interest in the topic I raised.

In the last article, I promised to describe:


And, as usual, the resulting code and the link to the demo chat at the end of the article.

Run multiple processes to handle connections


For a simple web socket server to work, a single process is enough, but to increase the number of simultaneous connections (and bypass the limit of 1024 simultaneous connections), as well as to use the resources of the entire processor (and not just one core), it is necessary that the web socket server uses several processes (optimally - number of processes = number of processor cores).
')
To run multiple processes, we will use the pcntl_fork() function. It creates a new process (child), which is almost a complete copy of the parent process that performs this call.
After calling pcntl_fork() algorithm forks: in case of successful execution of the function pcntl_fork() it returns the PID of the child process to the parent, and NULL to the child. If the creation of the fork fails, the pcntl_fork() function returns a value of −1).

 $pid = pcntl_fork(); //  //        if ($pid == -1) { //      } elseif ($pid) { //      } else { //      ,  PID      getmypid() } 


About the difference of the parent process from the child can be read on Wikipedia .

We can create as many child processes as we need in a loop:

 $childs = array(); for ($i=0; $i<4; $i++) { $pid = pcntl_fork(); //  if ($pid == -1) { die("error: pcntl_fork"); } elseif ($pid) { //  $childs[] = $pid; //   PID,     :) } else { //  break; //  ,        } } 


Interprocess communication


For the interaction between the parent and child processes, we will use sockets, namely the associated sockets:
The stream_socket_pair() function creates a pair of related indistinguishable stream sockets. Thus, we can write to one socket, and read data from the second.

 $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); //     fwrite($pair[0], ''); //    fread($pair[1], mb_strlen('')); //   


Now we combine this code with forks and we get:

 $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); //   $pid = pcntl_fork(); //  //        if ($pid == -1) { die("error: pcntl_fork"); } elseif ($pid) { //  fclose($pair[0]); //      $child = $pair[1]; //       } else { //  fclose($pair[1]); //      $parent = $pair[0]; //       } 


The resulting code for creating multiple child processes:

 $parent = null; $childs = array(); for ($i=0; $i<5; $i++) { $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); //   $pid = pcntl_fork(); //  if ($pid == -1) { die("error: pcntl_fork"); } elseif ($pid) { //  fclose($pair[0]); //      $childs[] = $pair[1]; //       } else { //  fclose($pair[1]); //      $parent = $pair[0]; //       break; //  ,        } } 

As a result of the operation of this code in the parent, the $childs array will contain all the sockets to communicate with the children, and the descendants will use $parent to communicate with the $parent .

Separation of processes into masters and workers


Since the child processes in our implementation are not directly related to each other and can only interact through the parent, it is advisable to separate the responsibilities between the parent and the descendants:

Also, a worker with us will be engaged in sending messages from scripts from the pages of the site or from the crown. To do this, we will create an additional socket, and add it to the array of listening sockets. For example, you can create a unix socket:
 $service = stream_socket_server('unix:///tmp/websocket.sock', $errorNumber, $errorString); 


Proxying webboxes using nginx


Nginx supports proxying of websockets since version 1.3.13. Thanks to nginx, you can handle connections to the webbox server on the same port as the site, as well as limit the number of open webboxes from one ip and other buns you like.

An example of an nginx config that allows it:
 limit_conn_zone $binary_remote_addr zone=perip:10m; server { listen 5.135.163.218:80; server_name sharoid.ru; location / { limit_conn perip 5; #  5   1 ip proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; #    } } 


Run from console


php websocket.php command php websocket.php or ./websocket.php (after giving permission to execute)
If you use nohup , for example, nohup ./websocket.php & , then the script will continue to work after closing the console.

By default, there are two restrictions on the number of connections per process.

As I already wrote, these restrictions can be circumvented using child processes (workers).

Integration with your framework on the example of yii


Since our master listens on an additional socket to communicate with our scripts (in the example above there was unix:///tmp/websocket.sock ), we can connect to this socket in any place of our website or in the crown and send a message that the master will send all workers, and they, in turn, all customers:
 $service = stream_socket_client ('unix:///tmp/websocket.sock', $errno, $errstr); fwrite($service, ' '); 

Using the yii component, it will look like this:
 Yii::app()->websocket->send(' '); 

More for yii
Download the extension , put it in the extensions/websocket
We put Websocket.php, WebsocketMasterHandler.php and WebsocketWorkerHandler.php from the folder sample / yii in the components folder.
In the commands folder we put from WebsocketCommand.php from the sample / yii folder.
In the main.php and console.php configs we insert into the components section:
 'websocket' => array( 'class' => 'Websocket', //'websocket' => 'tcp://127.0.0.1:8000', //'localsocket' => 'tcp://127.0.0.1:8001',// unix:///tmp/mysock //'workers' => 1 ), 

We also insert into the config console.php in the import section:
 'ext.websocket.*' 



Demonstration


Demo chat 2.0 (added list of users, added restriction: 1 message per second from a single IP)
It used the functions described above, and also corrected the shortcomings revealed after the publication of the previous article.
Demo Chat 1.0 (no user list, no limit)

I designed all source codes in the form of library and laid out on github

Update: If this topic is interesting to the community, the next article will be about how to make a simple game in which all participants will be on the same playing field and interact with each other in real time (the demo is almost ready).

The third part of the article: From chat to game: Battle City

Source: https://habr.com/ru/post/210228/


All Articles