📜 ⬆️ ⬇️

PHP web sockets. Part 3. From chat to game: Battle City

In the previous two parts ( Making web sites for PHP from scratch and Interprocess communication ), I used chats as a demonstration, but in this article, using the example of an online game, I will show that the scope of using web websites can be much broader.

As usual, at the end of the article links to the demo game and the source code on github.

Content:


Browser support for web sockets


Some people think that it’s too early to use web sockets, because they are not yet supported by all browsers. Therefore, if they are used, then only in conjunction with alternative transports: Adobe® Flash® Socket, AJAX long polling, AJAX multipart streaming, Forever Iframe, JSONP Polling.
')
Wikipedia tells us which browsers support web sites :
Google Chrome (since version 4.0.249.0);
Apple Safari (starting with version 5.0.7533.16);
Mozilla Firefox (since version 4);
Opera (since version 10.70 9067);
Internet Explorer (since version 10);

As we can see, the weakest link is Internet Explorer with versions less than a tenth. According to liveinternet statistics, for Russia - Internet Explorer with versions 9, 8, 7 and 6 has shares of 1.4, 1.7, 0.5 and 0.1 percent, respectively. Totally it turns out 3.7%. If you add to this figure more users with outdated versions of other browsers, then the final score may increase slightly, but I do not think that it will become more than 4%.
Based on this, everyone must decide for himself whether to support the zoo of alternative transports or forget about these users and live on.
For the sake of fairness, I want to say that the share of Internet Explorer is greater abroad, and the situation with the support of websockets is appropriate there. According to statistics from the Internet Explorer w3schools site with versions 9, 8, 7 and 6, the shares are 2.3, 3.1, 0.4 and 0.1 percent, respectively, which totals 5.9%

Online game development


So, now to the point. To demonstrate the work of the web socket server on php, I wanted to write a simple game. For a start, I needed to decide which one. Perhaps the only requirement for it was this:
all players must be on the same card and be able to interact with any other player

I’ve been googling for a long time on this topic until I came across this page in the toaster , where TravisBickle , the developer of phpdaemon, asks the community to suggest the idea of ​​a simple game that would demonstrate the work of web sockets. Despite the fact that some of the answers were quite interesting, this question is almost 3 years old ...
Of all the proposals, I chose “tanchiki”, but decided to make a simplified version of what was offered, and not a full-fledged game, so that the development process did not delay and the demo still saw the light, and did not remain in the halls of my mind.
Taking the chat code from the previous article, I added a little client part using:

On the server side, I slightly expanded the message handler from the client:

An example of a response from a server with three tanks
[{"Name": "adsa", "x": 25, "y": 38, "dir": "right", "health": 0}, {"name": "qwe", "x": 20, “y”: 18, “dir”: “right”, “health”: 0}, {“name”: “sgfd4”, “x”: 5, “y”: 7, “dir”: “right "," Health ": 0}]

Thus, I realized the tanks moving on the screen.

Since I was counting somewhere for 50 - 500 simultaneous players, it became clear that all the tanks on one screen do not fit, so I limited the scope of the tank to the size of the usual Battle City field, and also added a minimap. Due to the fact that the original black background is not clear, whether the tank is moving, or everything else, I had to use the texture. If you can suggest the best texture option, please leave a link to it in the comments.

The next step was shooting. To do this, it is necessary not only to process messages from customers, but also to trigger on a timer to calculate the movement of the projectile (I decided that 10 times per second would be enough or every 100,000 microseconds, respectively). Let me remind you that I used the stream_select(array &$read, array &$write, array &$except, int $tv_sec [, int $tv_usec = 0]) function stream_select(array &$read, array &$write, array &$except, int $tv_sec [, int $tv_usec = 0]) , which accepts arrays of sockets necessary for processing and it works either with changes in them, or on a timeout. It was decided to use the timeout feature of this function to implement timers, but, unfortunately, what happened was written in the documentation.
Using the stream_select function with a timeout is a bad idea. If you do decide, we recommend using timeouts of at least 200.000 microseconds.

With my timeout of 100,000 microseconds, the CPU usage was 100%.

In this regard, I decided that even if my tanks would not shoot, they still need to interact. So I began to handle their collisions :)
It was not clear which tank of the two points points for a head-on collision, as well as difficulties with a blow to the side. For this reason, I refused these two ambiguous types of contact and left only one remaining option - one tank rams the other behind.

On this, it seems, it was possible to stop - the goal of “interaction” was achieved, but we wanted more.
After spending some more time, I implemented timers using libevent (now my library works with both sream_select and libevent):
 $this->base = event_base_new(); $this->event = event_new(); event_set($this->event, $this->_server, EV_READ | EV_PERSIST, array($this, 'accept'), $this->base); event_base_set($this->event, $this->base); event_add($this->event); $timer = event_timer_new(); event_timer_set($timer, array($this, '_onTimer'), $timer); event_base_set($timer, $this->base); event_timer_add($timer, 100000); event_base_loop($this->base); 


Smart people write that event_timer is essentially a buffer that has a timeout, and I decided to look for whether it is possible to do something similar to stream_select, but, alas, to no avail . If you know how to do this, please write in the comments.

Now I managed to get around this problem like this:
 $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);//   $pid = pcntl_fork();//  if ($pid == -1) { die("error: pcntl_fork\r\n"); } elseif ($pid) { // fclose($pair[0]); $pair[1];//     ,       sream_select } else { //  fclose($pair[1]); $parent = $pair[0];//   ,       10    while (true) { fwrite($parent, '1'); usleep(100000); } } 

As a result, the processor load is about 0%.

Now nothing prevented me from adding the possibility of shooting, but, at the request of my friends, I decided to leave the possibility of "clashes."

Thanks


I would like to thank all those who paid my attention to the shortcomings in my code in the first two publications:
Skpd , pavlick , mayorovp , truezemez , Fesor , sovok_kpss , spein , seriyPS
Thank you very much and, of course, +1 in karma.
Thanks to you, you managed to achieve stability for the resulting library and a deeper understanding for me.

Demo and source code


Technical details:

Demo game (using stream_select)
Demo game (using libevent)
The source code of the library and examples is on github and is available under the MIT license.

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


All Articles