27 de Noviembre, 2014
□
General |
Tornado para rutinas de red asincronas y no bloqueantes |
“Tornado” es una librería para programas escritos en Python que
permite crear sistemas asíncronos y no bloqueantes para operaciones de
red, en donde cada petición ejecutada por los clientes puede ser
asíncrona. La forma en la que se encuentra implementada la librería,
permite escalar a varios miles de conexiones abiertas, algo que resulta
ideal para aplicaciones que requieren conexiones con un tiempo de vida
largo. Tornado no es una librería simple, de hecho es todo un framework
para diferentes tipos de elementos de red, muy similar a Twisted, pero
con la diferencia de que Tornado se centra en el desarrollo de
componentes de red asíncronos y no bloqueantes y Twisted es un framework
reactivo, centrado en el desarrollo de componentes de red que se
activan ante diferentes eventos.
El modelo tradicional para crear aplicaciones tales como servidores web
que soportan varios clientes de forma concurrente, se basa en un sistema
“multi-hilo”, en el que se crea un nuevo hilo por cada cliente que se
conecta al servicio. Esto da como resultado un consumo bastante alto de
recursos del sistema y problemas de rendimiento que pueden ser bastante
serios.
Un sistema “no-bloqueante” se basa en la ejecución de un único hilo de
forma continua y que responde a las peticiones de cada cliente de forma
asíncrona, de esta forma, el efecto de “bloqueo” de cada función se ve
muy disminuido, ya que una función asíncrona retorna antes de finalizar.
Por otro lado, últimamente se ha vuelto bastante popular el uso de
librerías como Tornado o AsyncIO (de la que se hablará en una próxima
entrada) no solamente en Python, sino en muchos otros lenguajes como
Java, PHP o Javascript para implementar rutinas asíncronas y sistemas no
bloqueantes. Es un modelo que ha ido cobrando fuerza por los beneficios
que aporta un sistema que consume pocos recursos comparado con los
sistemas clásicos, los cuales para manejar la concurrencia crean un
nuevo hilo de ejecución por cada cliente.
Para instalar Tornado, basta con ejecutar el comando “pip install
tornado” o descargar la última versión disponible en el repositorio
GitHub (https://github.com/tornadoweb/tornado) e instalar manualmente con el script “setup.py”.
Uno de los ejemplos más básicos a la hora de usar Tornado, consiste en
crear una aplicación web, para lo cual es necesario utilizar como mínimo
tres elementos: una o varias clases del tipo “RequestHandler” para el
procesamiento de las peticiones, uno o varios objetos del tipo
“Application” para gestionar y enrutar adecuadamente las peticiones
entrantes y finalmente, una función “main” que se encargará de iniciar
el servidor. El siguiente es un ejemplo muy simple para crear un
servidor web que procesa las peticiones “POST” de forma síncrona y las
peticiones “GET” de forma asíncrona.
BasicTornadoWebServer.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application, url, asynchronous
class HelloHandler(RequestHandler):
@asynchronous
def get( self ):
self .write( "Hello, world" )
def post( self ):
self .write( "Hello, world" )
app = Application([ url(r "/" , HelloHandler), ])
app.listen( 9090 )
IOLoop.current().start()
|
Como se puede apreciar, la clase “IOLoop” es la encargada de crear el
hilo de ejecución que se encargará de procesar cada petición entrante
por el puerto “9090”. Por otro lado, por defecto las funciones de una
instancia de “RequestHandler” son síncronas, esto quiere decir que el
hilo de ejecución principal será bloqueado hasta que la función retorne y
en este caso, para implementar una función asíncrona, se debe utilizar
el decorador “asynchronous”.
Se trata de un ejemplo muy simple y Tornado implementa muchas clases y
funciones que permiten crear aplicaciones web asíncronas y con elementos
tan interesantes como autenticación de usuarios, protección a CSRF o
cookies seguras. Estos elementos serán analizados con mayor detalle en
una próxima entrada, en esta nos centraremos en el uso de las utilidades
incluidas en Tornado para networking.
Elementos y utilidades en Tornado para operaciones de red asíncronas
Tal como se ha visto antes la utilidad “tornado.ioloop” es el elemento
principal para iniciar el hilo de ejecución en Tornado, no obstante no
solamente se puede utilizar para crear un servidor HTTP que permita el
procesamiento de peticiones, también es posible crear sockets TCP o UDP y
responder a cada cliente de forma asíncrona. El siguiente es un ejemplo
de cómo crear un servidor TCP básico con Tornado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import errno
import functools
import socket
from tornado import ioloop, iostream
def connection(sock, filedes, event):
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 )
stream = iostream.IOStream(connection)
stream.write( "HTTP/1.1 200 OK
Hello!
" , stream.close)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0 )
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
sock.setblocking( 0 )
sock.bind(("", 8080 ))
sock.listen( 1000 )
io_loop = ioloop.IOLoop.instance()
callback = functools.partial(connection, sock)
io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
try :
io_loop.start()
except KeyboardInterrupt:
io_loop.stop()
print "exited cleanly"
|
Las primeras instrucciones que ejecuta el script anterior resultan
bastante comunes para cualquier programador de Python que conoce el uso
de los sockets, en este caso se está vinculando el puerto “8080” y
posteriormente se utiliza una función de callback que será invocada
automáticamente por la utilidad “IOLoop” cuando un cliente realice una
petición al puerto 8080. La función de callback encargada de procesar
cada conexión es “connection” y como se puede apreciar, recibe como
argumento el socket servidor, el “file descriptor” y un listado de
eventos producidos durante la conexión. Las primeras instrucciones de la
función “connection” resultaran bastante comunes también, ya que lo
primero que se hace es aceptar la conexión iniciada por el cliente.
Posteriormente, se crea una instancia de la clase “iostream.IOStream”
recibiendo como argumento la conexión del cliente. Este elemento de
Tornado es lo que hace que este sencillo servidor TCP sea una rutina no
bloqueante, ya que se encarga de gestionar de forma asíncrona las
respuestas a todos los clientes que se conectan al servidor.

La clase “tornado.iostream.IOStream” es una de las clases que
extiende de “tornado.iostream.BaseIOStream”, la cual incluye las
funciones básicas para leer y escribir datos en sockets no bloqueantes.
Existen otras implementaciones tales como “iostream.SSLIOStream” o
“iostream.PipeIOStream” que implementan características extendidas.
Por otro lado, el modulo “tornado.netutil” incluye varias funciones
que son bastante útiles tanto para clientes como servidores. El uso de
algunas de dichas funciones se enseña a continuación.
1 2 3 4 5 6 7 8 9 10 11 12 | >>> from tornado import netutil
>>> sockets = netutil.bind_sockets( 8000 )
sockets [<socket._socketobject object at 0x7f067a1bf670 >, <socket._socketobject object at 0x7f067a1bf6e0 >]
>>> netutil.is_valid_ip('') False
>>> netutil.is_valid_ip( '192.168.1.2' ) True
>>> netutil.is_valid_ip( '192.168.1.999' ) False
>>> netutil.is_valid_ip( 'fe80::fe15:b4ff:fefc:f808' ) True
>>> netutil.is_valid_ip( 'aas10::fe15:b4ff:fefc:f808' ) False
>>> resDNS = netutil.Resolver()
>>> resDNS.configure( 'tornado.netutil.BlockingResolver' )
>>> resDNS.resolve( 'www.google.com' , 80 ) <tornado.concurrent.Future object at 0x7f0679b01bd0 > >>> dir (_)
[ '__class__' , '__delattr__' , '__dict__' , '__doc__' , '__format__' , '__getattribute__' , '__hash__' , '__init__' , '__module__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '__weakref__' , '_callbacks' , '_check_done' , '_done' , '_exc_info' , '_exception' , '_result' , '_set_done' , 'add_done_callback' , 'cancel' , 'cancelled' , 'done' , 'exc_info' , 'exception' , 'result' , 'running' , 'set_exc_info' , 'set_exception' , 'set_result' ]
|
La función “bind_sockets” se encarga de crear los sockets en todas
las interfaces de red disponibles y devuelve un listado con cada una de
las referencias creadas.
La función “is_valid_ip” se encarga de validar si una dirección IPv4 o
IPv6 es válida y finalmente, la clase “Resolver” permite configurar
varios de tipos de “resolvers” para peticiones DNS bloqueantes y no
bloqueantes.
Para mayor información sobre más utilidades disponibles en Tornado, se recomienda revisar la documentación: http://tornado.readthedocs.org/en/latest/netutil.html
Finalmente, Tornado incluye un servidor TCP que puede ser utilizado
como un envoltorio de la utilidad “IOLoop” descrita anteriormente. Dicho
servidor incluido en Tornado tiene la ventaja de que se encarga de
gestionar automáticamente el estado del bucle iniciado por “IOLoop”.
Existen 3 mecanismos que se pueden implementar con la utilidad TCPServer.
1. Un único proceso en ejecución.
1 2 | >>> from tornado import tcpserver >>> server = tcpserver.TCPServer() >>> server.listen( 8080 ) >>> from tornado import ioloop
>>> ioloop.IOLoop.instance().start()
|
2. Multi-proceso simple con las funciones estandar “bind” y “start”
1 2 3 4 5 6 | >>> from tornado import tcpserver
>>> server = tcpserver.TCPServer()
>>> server.bind( 8080 )
>>> server.start( 8080 )
>>> from tornado import ioloop
>>> ioloop.IOLoop.instance().start()
|
3. Multi-proceso avanzado utilizando la funcion “add_sockets”
1 2 3 4 5 6 7 8 9 | >>> from tornado import tcpserver
>>> from tornado import ioloop
>>> from tornado import netutil
>>> from tornado import process
>>> sockets = netutil.bind_sockets( 8000 )
>>> process.fork_processes( 0 )
>>>server = tcpserver.TCPServer()
>>>server.add_sockets(sockets)
>>>IOLoop.instance().start()
|
Este ha sido el uso básico de Tornado, sin embargo hay que aclarar
que existen otras librerías que son bastante robustas y que permiten
conseguir los mismos objetivos, como es el caso de AsyncIO o incluso
algunos módulos de Twisted. En el caso de AsyncIO, se encuentra incluida
por defecto en las últimas versiones de Python 3, concretamente a
partir de la versión 3.4. De dicha librería se hablará más adelante en
otro artículo. Fuente http://thehackerway.com/2014/11/27/tornado-para-rutinas-de-red-asincronas-y-no-bloqueantes/
|
|
publicado por
alonsoclaudio a las 08:51 · Sin comentarios
· Recomendar |
|
|
CALENDARIO |
 |
Febrero 2025 |
 |
|
DO | LU | MA | MI | JU | VI | SA | | | | | | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
|
|
| |
AL MARGEN |
Escuela de Educacion Secundaria Tecnica N 8 de Quilmes |
(Técnicos en Informática Personal y Profesional) |
| |
|