Introducción a Twisted y al desarrollo de servidores



Versión: 2.7

Introducción

¿Qué es Twisted?

Es un framework de red desarrollado íntegramente en Python que implementa una gran cantidad de protocolos. Utiliza el paradigma conocido como programación dirigida a eventos, en donde el flujo de un programa está determinado por los sucesos que ocurren durante la ejecución del mismo, previamente definidos por el programador o el sistem en sí. Twisted, como un paquete de Python, se originó en el año 2000, con el objetivo de proveer una solución a los problemas que establece el uso de sockets a bajo nivel, mediando con los hilos o threads y con las problemáticas que éste también presenta (por ejemplo, el acceso a datos compartidos).

Simplemente una breve descripción de cómo se originó el paquete; tema para el cual, posiblemente, habrá un futuro artículo.

Este artículo pretende introducir al programador a un sistema poderoso como complejo. Twisted tiene, en el día de hoy, 14 años de trayectoria como un framework que permite el desarrollo de clientes y servidores escalables y robustos.

Actualmente, la versión 13.2.0 de Twisted (la cual se utilizó para redactar este artículo) soporta Python 2.7 tanto 32 como 64 bits. El soporte para Python 3 se ha estado planeando desde hace un tiempo, y ya se ha portado más de la mitad del código para hacerlo compatible (véase Milestone Python-3.x).

Actualización 11/07/2016: Twisted ya soporta casi totalmente Python 3.

Twisted corre en diversas plataformas, Linux, Windows, Mac OS X, entre otras. Sin duda la plataforma de preferencia es aquella basada en Unix; sin embargo generalmente no se presentarán problemas al utilizarlo en las demás.

Descarga e instalación

pip o easy_install

Si bien es posible instalar Twisted utilizando cualquiera de estos métodos, no se recomienda. Mejor sigue leyendo…

Fuente

Instalar desde la fuente o Source Tarball es lo más recomendado. Para la versión 13.2.0, puedes descargar el archivo desde este enlace. Si en el momento que estás leyendo esto crees que hay una versión aún más nueva, mejor utiliza la sección de descargas de la página oficial.

Una vez que hayas obtenido el archivo, extráelo y, a continuación, ejecuta en la terminal:

python setup.py install

Asegúrate previamente de estar ubicado en la carpeta en donde se encuentra el archivo (utilizando el comando cd, por ejemplo, cd Descargas/Twisted-13.2.0).

Ubuntu / Debian

Hay aún una solución más sencilla para descargar e instalar Twisted para usuarios de Ubuntu y Debian. Abre la terminal y ejecuta:

sudo apt-get install python-twisted

De seguro preguntará por la constraseña del administrador. Ingrésala y luego de unos segundos ya tendrás Twisted instalado.

Sin embargo, prefiero el método anterior que garantiza la instalación de la última versión del paquete.

Windows

Todos los métodos anteriores instalan Twisted junto con sus dependencias (zope.interface). Para sistemas de la familia Microsoft Windows en la página oficial puedes encontrar los instaladores. Para la versión 13.2.0, aquí tienes los enlaces:

Python 2.7 (32-bit)

Python 2.7 (64-bit)

Y su principal dependencia:

Mac OS X

Generalmente Mac OS X incluye por defecto una instalación de Twisted. Según la página oficial, se recomienda instalar el paquete como se indica en el segundo método, desde las fuentes, en otra ubicación. De esta manera aplicaciones que utilicen la instalación por defecto no se verán afectadas.

Otros

Las últimas versiones de Twisted y sus dependencias puedes encontrarlas en este enlace, junto con las descargas para otras plataformas.

Una vez descargado e instalado, puedes probar Twisted con el siguiente código:

>>> import twisted
>>> twisted.version
Version('twisted', 13, 2, 0)

¡Has instalado Twisted correctamente!

Un poco más

Concurrencia

La programación multi-hilo conlleva, en la mayoría de los casos, a un código propenso a errores y comportamiento indefinido. La programación secuencial no es una opción, ya que esto no permitiría a un servidor servir datos a más de una conexión. Twisted soluciona esta problemática haciendo uso de la programación dirigida a eventos, en la cual se ejecuta un único bucle principal que se encarga de llamar a las funciones definidas por el programador (o el sistema en sí) denominadas eventos. De esta manera, en un servidor que sirve información a varios clientes, es posible compartir la información entre todas las conexiones sin necesidad de preocuparse por bloquear o permitir el acceso a la misma.

Teniendo en cuenta esta información, un servidor o cliente siempre manejará las conexiones (junto con todos sus eventos) en un mismo hilo, sin importar las prestaciones del hardware. Por ejemplo, en un procesador de 4 núcleos, Twisted no se encargará de distribuir sus tareas entre estos para acelerar el proceso; aunque no quiere decir que no pueda ser implementado por el programador.

Sin embargo, este apartado no pretende más que informar al lector sobre la arquitecura y forma de trabajar de Twisted.

Comunidad

Con la trayectoria de Twisted se ha desarrollado una amplia comunidad. Principalmente, aquella con más actividad es StackOverflow, en donde todas sus dudas pueden ser, en la mayoría de los casos, respondidas por los usuarios bajo la etiqueta twisted. Entre las otras vías para vertir sus comentarios o dudas se encuentran las listas de correo y el canal de IRC en irc.freenode.net bajo el nombre #twisted.

Aplicación

Es hora de ver algo de código, Twisted en acción. Empezaremos por crear un servidor el cual reenvía todo lo que recibe. Luego, un cliente básico usando el módulo estándar socket para probar el código.

Servidor

Este código se encuentra en la página principal de Twisted:

from twisted.internet import protocol, reactor

class Echo(protocol.Protocol):
    def dataReceived(self, data):
        self.transport.write(data)

class EchoFactory(protocol.Factory):
    def buildProtocol(self, addr):
        return Echo()

reactor.listenTCP(1234, EchoFactory())
reactor.run()

Tal vez al ver este código puedas deducir todo, parte o nada de cómo funciona Twisted. De todas maneras, pasaremos a la explicación.

from twisted.internet import protocol, reactor

En esta primera línea se importan los módulos twisted.internet.protocol y twisted.internet.reactor.

Protocol

Como se dijo, éste es un simple programa servidor que reenvía todo lo que recibe. Para lograr esto hay que establecer un protocolo. Es por esa razón que se crea una nueva clase, Echo, de la cual habrá una instancia por cada conexión. El método dataReceived es un evento, el cual, lógicamente, será llamado por cada porción de datos que se haya recibido. Dichos datos se pasan al evento en su único argumento, data, el cual se utiliza luego para enviar al cliente lo que se ha recibido.

class Echo(protocol.Protocol):
    def dataReceived(self, data):
        self.transport.write(data)

self.transport es una instancia de twisted.internet.tcp.Server, a través de la cual enviamos datos hacia el cliente.

Factory

El responsable de crear una instancia de Echo para cada cliente que se conecta a nuestro servidor es la clase EchoFactory, una instancia de twisted.internet.protocol.Factory. De allí el nombre fábrica, ya que se encarga de fabricar protocolos para cada conexión entrante.

Tal como dataReceived lo es en Protocol, buildProtocol es un evento el cual será llamado cada vez que se encuentre una conexión entrante, para asignarle un protocolo. De esta manera cada conexión estará atada a un protocolo especificado por el programador en este método. En este caso, todas las conexiones serás manejadas a través del mismo protocolo: Echo, el cual reenvía todo lo que recibe.

class EchoFactory(protocol.Factory):
    def buildProtocol(self, addr):
        return Echo()

El argumento que recibe buildProtocol es una instancia de IPv4Address o IPv6Address, según corresponda. La misma contiene información del cliente, la conexión entrante, tal como la dirección de IP y puerto, entre otras. Estos datos también pueden ser accedidos una vez en el protocolo, a través de la función self.transport.getPeer.

reactor

reactor es el bucle principal de Twisted del cual hablé al comienzo del artículo, aquel que se encarga de llamar a los eventos en el momento apropiado, y alternando entre las distintas conexiones para lograr (más bien simular) la concurrencia.

reactor.listenTCP(1234, EchoFactory())
reactor.run()

En este caso se emplea para escuchar conexiones TCP a través del puerto 1234. Como segundo parámetro se pasa una instancia de nuestra fábrica, aquella que, como indiqué anteriormente, se encarga de asignarle un protocolo a cada conexión entrante.

Por último, nada ocurrirá si no ejecutamos el bucle principal, haciendo uso de la función reactor.run().

Cliente

De seguro también habrá otro artículo para el desarrollo de clientes con Twisted. En este caso sólo crearemos un simple cliente para conectar a nuestro servidor que sí fue desarrollado con Twisted:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from socket import socket

s = socket()
s.connect(("127.0.0.1", 1234))

while True:
    output_data = raw_input("> ")
    if output_data:
        s.send(output_data)
        input_data = s.recv(1024)
        if input_data:
            print input_data

Ejecutando

Guarda el código del servidor en un archivo de Python (yo lo he llamado server.py). Haz lo mismo con el cliente. Luego, ejecuta el servidor y, a continuación, el cliente. Escribe lo que se te ocurra y verás cómo el servidor responde con lo que ha recibido.

Éste es el resultado que yo obtuve:

> hola mundo...
hola mundo...
> ... usando Twisted.
... usando Twisted.
> Gracias por leer este articulo!
Gracias por leer este articulo!

Puedes descargar ambos scripts desde este enlace.

Convenciones de nombramiento

Para algunos podrá ser un impedimiento, una complicación, una molestia, o, simplemente, no afectar en lo absoluto. Pero me parece importante destacar el tema, sobretodo para aquellos que se introducen en el desarrollo de aplicaciones con el framework.

Twisted tiene su propia guía de estilo para el código Twisted, la cual concuerda en varios aspectos con la guía de estilo para el código Python (PEP 8). Una de las diferencias más importantes que pueden notarse a simple vista es el estilo de nombramiento para las funciones. Según la guía de estilo de Python, las funciones deben ser nombradas utilizando minusculas_con_guiones_bajos (lower_case_with_underscores). En cambio, Twisted está desarrollado por completo utilizando minusculaMayuscula (camelCase o mixedCase). Hay dos factores por los cuales las convenciones difieren entre ambas guías. La primera, es que a Glyph, creador de Twisted, le agrada mixedCase mucho más que lower_case_with_underscores. La segunda, indica que las convenciones de nombramiento de Twisted fueron establecidas antes de la creación del PEP 8. Glyph explica en primera persona todo lo que tienes que saber sobre el tema en este enlace.

Tal como lo indica el PEP 8, la consistencia dentro de un proyecto es lo más importante. Al escribir código utilizando principalmente Twisted, mi recomendación es que utilices mixedCase para nombrar a funciones y variables. Esto mantendrá el código consistente, posiblemente, en un gran porcentaje. Aunque reitero, es mi recomendación. Por ejemplo, no es mala idea utilizar lower_case_with_underscores, tal como lo indica la guía de código Python, para las funciones propias de tu aplicación, así mantienes una convención específica para código propio y otra para eventos y funciones de Twisted.

Para terminar, ambas son únicamente guías. Ninguna de ellas te obliga a seguir sus cláusulas, son sólo recomendaciones. A continuación una cita del PEP 8 que se aplica muy bien al tema:

Pero lo más importante: saber cuando ser inconsistente –simplemente en ciertas ocasiones la guía de estilo no se aplica. Cuando estés en duda, utiliza tu mejor criterio. Observa otros ejemplos y decide cual se ve mejor. ¡Y no dudes en preguntar!

 

Enlaces interesantes



4 comentarios.

  1. Miguel Barraza says:

    hola, ¡gracias por compartir tanto conocimiento!. Después de leer tu post me quedó una duda: ¿Cómo hago para indicar al servidor cuantos bytes debe leer?.
    Veo en el cliente que estás leyendo 1024, pero en el servidor no se indica en ningún lado. Estoy probándolo en python 3.

    Saludos y gracias.

    • Recursos Python says:

      Hola, me alegro que te haya servido. Twisted se encarga de la lectura de datos a través del protocolo que integra por defecto (protocol.Protocol). Si realmente necesitas especificar cuántos bytes se deben leer, tendrías que crear tu propio protocolo (véase este enlace).

      Saludos!

Deja un comentario