📜 ⬆️ ⬇️

Quickpong - development of a network game based on the Twisted framework

Developed and launched on the domain quickpong.com online version of the game Pong. In the game (by design), only the multiplayer mode is implemented, that is, the game goes not against artificial intelligence, but against another person.

The game is a client-server application, the server part is written on the Python framework Twisted , and the client part is written on the FlashPunk flash framework.

This is my first experience in developing an asynchronous network application capable of serving thousands of simultaneous connections. Next, I will talk about how this program works, what problems I had to face when developing, what ideas I wanted to implement and what ultimately remained unrealized. Perhaps my experience will be useful for someone.
')

Two words about the gameplay


On the playing field, to the left and to the right there are 2 boards that can move vertically. Each of the boards is controlled by a person. A ball is moving between the boards, bouncing off the walls of the playing field and boards. The player's task is to prevent the ball from hitting the wall, near which its board is located.

Selection of technologies used


When mastering new technologies and tools, the availability of competent detailed documentation plays a huge role. Choosing a server framework, I considered Python's Twisted, Tornado, and, on everyone's lips, Node.js.

Based on the fact that I had no experience at all in developing such applications, my choice fell on Twisted. A very detailed introductory course has been written for it, explaining the basics of developing asynchronous applications, regardless of any framework or programming language, and, of course, telling about the use of Twisted itself. I strongly recommend this course to anyone who wishes to understand the basics of developing asynchronous applications. On the link above you can find and translate this course into Russian.

In this project for me the main interest was the development of the server part, so for the development of the client I decided not to bother much and to make it on a flash, I already had experience with the use of the FlashPunk framework.

Algorithm of client and server interaction


I did not approach the choice of the interaction algorithm between the client and the server as thoughtfully as the choice of frameworks. As a result, I implemented the working version of the algorithm only from the third time, and invented the first two versions of the algorithm myself and after two failures I found the third version on the Internet.

The main difficulty in such applications is the synchronization of clients; it is impossible to allow one client to see one picture at one and the same time, the second client to another.

In addition, I would like to avoid fraud on the part of clients: if desired, and in skill, the client's swf can be modified and can transmit cheat data to the server.

Thus, in the first version of the algorithm, I decided to act as follows:
  1. each client himself calculates the state of his game world (the position of the ball, his board and the opponent's board),
  2. 20 times a second each client transmits to the server information about the change in the position of its board and information about the ball parameters (coordinates, vector, speed) in its game world,
  3. the server sends each player information about the position of the opponent's board,
  4. the server itself calculates the current state of the game world and compares it with the data received from customers (paragraph 2). If there is an out of sync between the states calculated by the client and the server, then the server forcibly returns the clients to a state that it considers correct.
The algorithm turned out to be inoperative. He could be a worker only if all 3 participants in the data exchange had the same state. In practice, in 100% of the cases it was dissynchronized and it was impossible to play.

In the second version of the algorithm, I decided to get rid of one of the links - from calculating the state of the game world on the server:
  1. each client still calculated the coordinates of the ball and the boards and transmitted data about his condition to the server,
  2. the server compared client states and if they were different, then forcibly returned one of the clients to the state of the second.
This approach also turned out to be inoperative. Even clients running on two identical machines calculated the state of the game world a little differently and, despite the fact that I now compared 2 states, not 3, as in the previous case, the state of one of the clients was constantly forcibly changed, it looked like noticeable “jumps” of the ball and playing was impossible.

It became clear that, firstly, the state of the game world should be calculated only in one place, on the server, and, secondly, before continuing the experiments, to avoid this waste of time, I should study the theory and experience of other developers.

Googling I found such interesting links:

The algorithm described by the last link was taken by me for this project. Its essence is as follows:
  1. All calculations are carried out only on the server. Clients transmit to the delta server their status changes, in my case this is a change in the position of the board relative to the previous data transfer.
  2. The server calculates the position of the boards (suppressing possible cheating from the client) and the ball and sends the generated data frame to the clients.
  3. Clients render the received data frames, in my case, with a delay of 3 frames. This is an important point. Data from the server to the client comes unevenly, for example, because of problems with the network, the client may receive data on the new position of the ball not in the set 50 ms, but in 60-70 ms. That is, it may happen that the last data frame is already rendered, and the new one has not yet arrived. In such a situation, the client will have no data for drawing the ball and, in my case, the ball will simply stop waiting for receiving a new piece of data. In order to prevent such situations, the client draws data with a delay of 3 data frames, which gives some margin, thanks to which even if the data from the server is not received in time, the client will have something to render. The unpleasant side of this algorithm is a noticeable delay between the user's action (pressing the buttons on the keyboard) and the display of changes on the screen. There are methods to eliminate this delay, but specifically in this case I decided not to complicate the client.


Implementation


I posted the server and client sources on Github:

This diagram: quickpong.com/images/quickpong.png shows the logic of the client and server interaction.

From the point of view of server implementation, everything is quite transparent. At port 10080, a reactor (event loop) starts, in which the server factory operates — the QuickpongServerFactory class. During factory initialization, an instance of the Quickpong class is created , which contains all the logic for the interaction between the server and the client.

When a new client is connected, the factory calls the buildProtocol method and creates for each joined client an instance of the QuickpongProtocol class, the created object is passed to Quickpong. Thus, an object of the Quickpong class has access to all the clients that have joined and can perform the necessary work with them: pair up, calculate the state of the game world, etc.

The object of the class QuickpongProtocol contains only methods for receiving and transmitting data from / to the client.

With the implementation of the client, too, everything is simple, the only interesting point was the following. Using FlashPunk, I can set the picture refresh rate (FPS), while FlashPunk can guarantee that it will draw N frames per second, but cannot guarantee that each frame will be drawn in 1 / N seconds. That is, with FPS 50, in the ideal case, each frame should be rendered in 20 ms, in the real case one frame can be drawn in 15 ms, and another frame in 25 ms. If the ball moves at a constant speed, for example 10 pixels per second, and the drawing of each data frame matches the FlashPank frame drawing, then the ball will move unevenly, jerking, as in one case it will move 10 pixels for 15 ms, and in another for 25.

This feature had to be taken into account in the client and before the frame is drawn, I check how much time has passed since the previous frame was drawn, based on this I determine to render the data frame in whole or in part.

Testing and monitoring


The most interesting question for me is how many online players can sustain this server? For the test, I wrote a small client on python that emulated human actions.

The test was conducted on a virtual machine, under which 1 Intel® Xeon® CPU core E31275 @ 3.40GHz was allocated.

By means of the same Twisted, I hung a web server on port 10082, which lists the number of users online and the number of active games separated by a comma. Based on this information, as well as using the python library psutil and the rrdtool + py-rrdtool bundles, I wrote scripts that display information about the current number of online users and resources consumed in a digestible form: quickpong.com/stats.html (images are updated every minute)

With 5000 (5 thousand) players, the program eats off about 100 MB of RAM, loads CPU by an average of 30-40%.

Roadmap


Ideas that remain unfulfilled:

At the moment I have lost interest in the development of this project, perhaps my work will seem informative or interesting to someone, so I put all the sources on GitHub:

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


All Articles