📜 ⬆️ ⬇️

Asynchronous punch

image As someone has probably guessed, this article will deal with sockets, and frameworks that facilitate working with them. Recently, I started working on a new project, an online game. For such projects, the response time from the server is quite critical, if it is certainly not a step-by-step strategy, although perhaps in this case too. So how can this be achieved with severe resource constraints?

The main question remains, what will happen when the user performs an action? Regular ajax requests will not work, it is quite clear why. Therefore sockets come to our aid.

Precedence

The game is not built on a flash, but js does not know how to work with sockets, so you can use a flash substrate to deal with this issue. Here a small jsocket library will help us.
For the server, first of all, the attention fell on Twisted, and even began to write something, as I discovered for myself another mountain of interesting tools, of which gevent and tornado liked the most. Looking for information about each of them found an interesting article , and later this . However, there is considered a slightly different task than I need, so I decided to conduct my testing.

Scripts

We write a simple client for tests on twisted.

from twisted.internet.protocol import ClientFactory from twisted.protocols.basic import LineReceiver from twisted.internet import epollreactor epollreactor.install() from twisted.internet import reactor import sys,time clients=30000 host='127.0.0.1' port=8001 file = open( 'log.dat', 'w') class glob(): connections=0 crefuse=0 clost=0 def enchant(self): self.connections+=1 def refuse(self): self.crefuse+=1 def lost(self): self.clost+=1 a=glob() class EchoClient(LineReceiver): measure=True def connectionMade(self): self.sendLine("Hello, world!") self.t1 = time.time() def lineReceived(self, line): if self.measure: self.t2 = time.time() - self.t1 file.write('%s %s %s %s %s\n' % (a.connections+1,self.t2,a.crefuse,a.clost,line)) self.measure=False if a.connections+1 < clients: a.enchant() reactor.connectTCP(host, port, EchoClientFactory()) else: self.transport.loseConnection() class EchoClientFactory(ClientFactory): protocol = EchoClient def clientConnectionFailed(self, connector, reason): a.refuse() def clientConnectionLost(self, connector, reason): a.lost() def main(): f = EchoClientFactory() reactor.connectTCP(host, port, f) reactor.run() file.close() if __name__ == '__main__': main() 

And the server on each framework.

gevent

 clients=[] host='' port = 8001 def echo(socket, address): clients.append(socket) while True: line = socket.recv(1024) for client in clients: try: client.send(str(len(clients))+'\r\n') except: clients.remove(client) if __name__ == '__main__': from gevent.server import StreamServer StreamServer((host, port), echo).serve_forever() 


tornado

 import errno import functools import socket from tornado import ioloop, iostream host='' port = 8001 clients=[] class Connection(object): def __init__(self, connection): clients.append(self) self.stream = iostream.IOStream(connection) self.read() def read(self): self.stream.read_until('\r\n', self.eol_callback) def eol_callback(self, data): for c in clients: try: c.stream.write(str(len(clients))+'\r\n') except: clients.remove(c) self.read() def connection_ready(sock, fd, events): while True: try: connection, address = sock.accept() except socket.error, e: if e[0] not in (errno.EWOULDBLOCK, errno.EAGAIN): raise return connection.setblocking(0) Connection(connection) if __name__ == '__main__': sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setblocking(0) sock.bind((host, port)) sock.listen(30000) io_loop = ioloop.IOLoop.instance() callback = functools.partial(connection_ready, sock) io_loop.add_handler(sock.fileno(), callback, io_loop.READ) try: io_loop.start() except KeyboardInterrupt: io_loop.stop() print "exited cleanly" 

')
twisted

 from twisted.protocols import basic class MyChat(basic.LineReceiver): def connectionMade(self): self.factory.clients.append(self) def connectionLost(self, reason): self.factory.clients.remove(self) def dataReceived(self, line): for c in self.factory.clients: c.message(str(len(factory.clients))+'\r\n') def message(self, message): self.transport.write(message) from twisted.internet import epollreactor epollreactor.install() from twisted.internet import reactor, protocol from twisted.application import service, internet factory = protocol.ServerFactory() factory.protocol = MyChat factory.clients = [] reactor.listenTCP(8001,factory) reactor.run() 


A little explanation

In fact, we created a chat server. As soon as the server receives a string from the client, it sends this string to all connected clients (or rather, we send the number of connected clients instead). Thus, the load increases arithmetically. The client, in turn, measures the elapsed time between sending a message to the server and receiving a response from it. Since our current client connected last, all clients by this point will already receive a response.

When measuring, I took the dependence of the response time on the number of connected users. The client and server were started on lokalkhost. The machine is used as a desktop, so the results may be slightly distorted, but the essence is clear. It was planned to measure the dependency up to 30,000 podlyucheny, but unfortunately for all three frameworks it turned out to be too long.
image
On the response axis, time, of course, in seconds.
The tornado graph shows two lines. These are not two tests, but one, just the results are as follows:
11476 2.45670819283
11477 0.405035018921
11478 2.42619085312
11479 0.392680883408
11480 2.5216550827
11481 0.401995897293

where the first number is the number of connections, and the second response time. I do not know what it is connected with.

findings

I give a list of all the pros and cons, in the framework of the task
Gevent


Twisted


Tornado


Well, from all of the above, everyone can draw conclusions for himself. I will choose gevent for myself and my tasks.

UPD. a little wrong with the script on gevent. It had less load. I tried it on, the result was a little worse, but it was still the best. here are the approximate results:
10336 1.01536607742 0 0 10338
10337 0.955881118774 0 0 10339
10338 0.947958946228 0 0 10340
10339 1.02578997612 0 0 10341

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


All Articles