asyncore — controlador de socket asincrónico

Código fuente: Lib/asyncore.py

Obsoleto desde la versión 3.6: asyncore will be removed in Python 3.12 (see PEP 594 for details). Please use asyncio instead.


Nota

Este módulo solo existe para compatibilidad con versiones anteriores. Para el nuevo código recomendamos usar asyncio.

Este módulo proporciona la infraestructura básica para escribir servicio de socket asincrónicos, clientes y servidores.

Sólo hay dos maneras de que un programa en un solo procesador haga «más de una cosa a la vez». La programación multiproceso es la forma más sencilla y popular de hacerlo, pero hay otra técnica muy diferente, que le permite tener casi todas las ventajas de multiproceso, sin usar realmente varios subprocesos. Es realmente sólo práctico si su programa está en gran parte limitado por el I/O. Si el programa está limitado por el procesador, los subprocesos programados preventivos son probablemente lo que realmente necesita. Sin embargo, los servidores de red rara vez están limitado al procesador.

Si su sistema operativo es compatible con la llamada del sistema select() en su biblioteca de I/O (y casi todos lo son), puede usarla para hacer malabares con varios canales de comunicación a la vez; haciendo otro trabajo mientras su I/O está teniendo lugar en el «fondo». Aunque esta estrategia puede parecer extraña y compleja, especialmente al principio, es en muchos sentidos más fácil de entender y controlar que la programación multiproceso. El módulo asyncore resuelve muchos de los problemas difíciles para usted, haciendo que la tarea de construir sofisticados servidores de red de alto rendimiento y clientes sea fácil. Para aplicaciones y protocolos «conversacionales», el módulo complementario asynchat es invaluable.

La idea básica detrás de ambos módulos es crear uno o más canales de red, instancias de clase asyncore.dispatcher y asynchat.async_chat. La creación de los canales los agrega a un mapa global, utilizado por la función loop() si no lo proporciona con su propio map.

Una vez creados los canales iniciales, llamar a la función loop() activa el servicio de canal, que continúa hasta que se cierra el último canal (incluido el que se ha agregado al mapa durante el servicio asincrónico).

asyncore.loop([timeout[, use_poll[, map[, count]]]])

Ingresa un bucle de sondeo que termina después de que se hayan cerrado los pases de conteo o todos los canales abiertos. Todos los argumentos son opcionales. El parámetro count tiene como valor predeterminado None, lo que da como resultado que el bucle termine solo cuando se hayan cerrado todos los canales. El argumento timeout establece el parámetro de tiempo de espera para la llamada adecuada a select() o poll(), medida en segundos; el valor predeterminado es 30 segundos. El parámetro use_poll, si es true, indica que poll() debe utilizarse en lugar de select() (el valor predeterminado es False).

El parámetro map es un diccionario cuyos elementos son los canales a observar. A medida que se cierran los canales, se eliminan del mapa. Si se omite map, se utiliza un mapa global. Los canales (instancias de asyncore.dispatcher, asynchat.async_chat y subclases de los mismos) se pueden mezclar libremente en el mapa.

class asyncore.dispatcher

La clase dispatcher es un contenedor fino alrededor de un objeto de socket de bajo nivel. Para hacerlo más útil, tiene algunos métodos para el control de eventos que se llaman desde el bucle asincrónico. De lo contrario, se puede tratar como un objeto de socket normal sin bloqueo.

La activación de eventos de bajo nivel en determinados momentos o en determinados estados de conexión indica al bucle asincrónico que se han producido ciertos eventos de nivel superior. Por ejemplo, si hemos pedido un socket para conectarse a otro host, sabemos que la conexión se ha realizado cuando el socket se vuelve grabable por primera vez (en este punto sabe que puede escribir a él con la expectativa de éxito). Los eventos de nivel superior implícitos son:

Evento

Descripción

handle_connect()

Implícito en el primer proceso de lectura o escritura

handle_close()

Implícito en un evento de lectura sin datos disponibles

handle_accepted()

Implícito en un evento de lectura en un socket de escucha

Durante el procesamiento asincrónico, se utilizan los métodos readable() y writable() de cada canal asignado para determinar si el socket del canal deberían ser agregados a la lista de canales seleccionados o encuestados para eventos de lectura y escritura.

Por lo tanto, el conjunto de eventos de canal es mayor que los eventos básicos del socket. El conjunto completo de métodos que se pueden invalidar en la subclase es el siguiente:

handle_read()

Se llama cuando el bucle asincrónico detecta que una llamada read() en el socket del canal tendrá éxito.

handle_write()

Se llama cuando el bucle asincrónico detecta que se puede escribir un socket grabable. A menudo, este método implementará el almacenamiento en búfer necesario para el rendimiento. Por ejemplo:

def handle_write(self):
    sent = self.send(self.buffer)
    self.buffer = self.buffer[sent:]
handle_expt()

Se llama cuando hay datos fuera de banda (OOB) para una conexión de socket. Esto casi nunca sucederá, ya que OOB es tenuemente compatible y rara vez se utiliza.

handle_connect()

Se llama cuando el socket del abridor activo realmente hace una conexión. Puede enviar un banner de «bienvenido» o iniciar una negociación de protocolo con el punto de conexión remoto, por ejemplo.

handle_close()

Se llama cuando el socket está cerrado.

handle_error()

Se llama cuando se genera una excepción y no se controla de otro modo. La versión predeterminada imprime un traceback condensado.

handle_accept()

Se llama en los canales de escucha (abridores pasivos) cuando se puede establecer una conexión con un nuevo punto de conexión remoto que ha emitido una llamada connect() para el punto de conexión local. En desuso en la versión 3.2; use handle_accepted() en su lugar.

Obsoleto desde la versión 3.2.

handle_accepted(sock, addr)

Se llama en los canales de escucha (abridores pasivos) cuando se ha establecido una conexión con un nuevo punto de conexión remoto que ha emitido una llamada connect() para el punto de conexión local. sock es un objeto de socket new utilizable para enviar y recibir datos en la conexión, y addr es la dirección enlazada al socket en el otro extremo de la conexión.

Nuevo en la versión 3.2.

readable()

Se llama en cada momento alrededor del bucle asincrónico para determinar si se debe agregar el socket de un canal a la lista en la que se pueden producir eventos de lectura. El método predeterminado simplemente retorna True, lo que indica que, de forma predeterminada, todos los canales estarán interesados en eventos de lectura.

writable()

Se llama cada vez alrededor del bucle asincrónico para determinar si se debe agregar el socket de un canal a la lista en la que se pueden producir eventos de escritura. El método predeterminado simplemente retorna True, lo que indica que, de forma predeterminada, todos los canales estarán interesados en eventos de escritura.

Además, cada canal delega o extiende muchos de los métodos de socket. La mayoría de estos son casi idénticos a sus socios de socket.

create_socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

Esto es idéntico a la creación de un socket normal y usará las mismas opciones para la creación. Consulte la documentación socket para obtener información sobre la creación de sockets.

Distinto en la versión 3.3: Los argumentos family y type se pueden omitir.

connect(address)

Al igual que con el objeto de socket normal, address es una tupla con el primer elemento al que se va a conectar el host y el segundo el número de puerto.

send(data)

Envía data al punto final remoto del socket.

recv(buffer_size)

Lee como máximo los bytes buffer_size desde el punto final remoto del socket. Un objeto bytes vacío implica que el canal se ha cerrado desde el otro extremo.

Tenga en cuenta que recv() puede lanzar BlockingIOError, aunque select.select() o select.poll() ha informado del socket listo para la lectura.

listen(backlog)

Escucha las conexiones realizadas al socket. El argumento backlog especifica el número máximo de conexiones en cola y debe ser al menos 1; el valor máximo depende del sistema (normalmente 5).

bind(address)

Enlaza el socket a address. El socket no debe estar enlazado ya. (El formato de address depende de la familia de direcciones — consulte la documentación socket para obtener más información.) Para marcar el socket como reutilizable (estableciendo la opción SO_REUSEADDR), llame al método set_reuse_addr() del objeto dispatcher.

accept()

Acepta una conexión. El socket debe estar enlazado a una dirección y escuchar las conexiones. El valor retornado puede ser None o un par (conn, address) donde conn es un objeto de socket new utilizable para enviar y recibir datos en la conexión, y address es la dirección enlazada al socket en el otro extremo de la conexión. Cuando se retorna None significa que la conexión no se llevó a cabo, en cuyo caso el servidor debe ignorar este evento y seguir escuchando otras conexiones entrantes.

close()

Cierra el socket. Se producirá un error en todas las operaciones futuras en el objeto de socket. El punto final remoto no recibirá más datos (después de vaciar los datos en cola). Los sockets se cierran automáticamente cuando se recogen como elementos no utilizados.

class asyncore.dispatcher_with_send

Una subclase dispatcher que agrega capacidad de salida almacenada en búfer simple, útil para clientes simples. Para un uso más sofisticado, utilice asynchat.async_chat.

class asyncore.file_dispatcher

Un file_dispatcher toma un descriptor de archivo o file object junto con un argumento de mapa opcional y lo ajusta para su uso con las funciones poll() o loop(). Si se proporciona un objeto de archivo o cualquier cosa con un método fileno(), ese método se llamará y se pasará al constructor file_wrapper.

Disponibilidad: Unix.

class asyncore.file_wrapper

Un file_wrapper toma un descriptor de archivo entero y llama a os.dup() para duplicar el identificador de modo que el identificador original se pueda cerrar independientemente del file_wrapper. Esta clase implementa métodos suficientes emulando un socket para su uso por la clase file_dispatcher.

Disponibilidad: Unix.

Ejemplo asyncore de cliente HTTP básico

Aquí hay una llamada básica al cliente HTTP que usa la clase dispatcher para implementar su controlador de socket:

import asyncore

class HTTPClient(asyncore.dispatcher):

    def __init__(self, host, path):
        asyncore.dispatcher.__init__(self)
        self.create_socket()
        self.connect( (host, 80) )
        self.buffer = bytes('GET %s HTTP/1.0\r\nHost: %s\r\n\r\n' %
                            (path, host), 'ascii')

    def handle_connect(self):
        pass

    def handle_close(self):
        self.close()

    def handle_read(self):
        print(self.recv(8192))

    def writable(self):
        return (len(self.buffer) > 0)

    def handle_write(self):
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]


client = HTTPClient('www.python.org', '/')
asyncore.loop()

Ejemplo asyncore de servidor de eco básico

Aquí hay un servidor de eco básico que utiliza la clase dispatcher para aceptar conexiones y distribuye las conexiones entrantes a un controlador:

import asyncore

class EchoHandler(asyncore.dispatcher_with_send):

    def handle_read(self):
        data = self.recv(8192)
        if data:
            self.send(data)

class EchoServer(asyncore.dispatcher):

    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket()
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)

    def handle_accepted(self, sock, addr):
        print('Incoming connection from %s' % repr(addr))
        handler = EchoHandler(sock)

server = EchoServer('localhost', 8080)
asyncore.loop()