selectors — Abstracciones de multiplexación de E/S

Propósito:Proporciona abstracciones independientes de la plataforma para multiplexación de E/S basada en el módulo select.

El módulo selectors proporciona una capa de abstracción independiente de la plataforma sobre las funciones de supervisión de E/S específicas de la plataforma en select.

Modelo operativo

Las interfaces de programación en selectors están basadas en eventos, similar a poll() de select. Hay varias implementaciones y el módulo establece automáticamente el alias DefaultSelector para referirse a la más eficiente para la configuración actual del sistema.

Un objeto selector proporciona métodos para especificar qué eventos ver en un conector, y luego deja que la persona que llama espere eventos en una manera independiente a la plataforma. Registrar el interés en un evento crea un SelectorKey, que contiene el conector, información sobre los eventos de interés, y datos de aplicación opcionales. El dueño del selector llama a su método select() para saber sobre los eventos. El valor de retorno es una secuencia de objetos clave y una máscara de bits que indica qué eventos han ocurrido. Un programa que usa un selector debe llamar repetidamente select(), para luego manejar los eventos apropiadamente.

Servidor de eco

El ejemplo del servidor de eco a continuación utiliza los datos de la aplicación en el SelectorKey para registrar una función de devolución de llamada para ser invocada en el nuevo evento. El bucle principal obtiene la devolución de llamada de la llave y pasa el conector y la máscara de evento para ella. Cuando el servidor se inicia, se registra la función accept() que se llamará para leer eventos en el conector principal del servidor. La aceptación de la conexión produce un nuevo conector, que luego se registra con la función read() como una devolución de llamada para leer eventos.

selectors_echo_server.py
import selectors
import socket

mysel = selectors.DefaultSelector()
keep_running = True


def read(connection, mask):
    "Callback for read events"
    global keep_running

    client_address = connection.getpeername()
    print('read({})'.format(client_address))
    data = connection.recv(1024)
    if data:
        # A readable client socket has data
        print('  received {!r}'.format(data))
        connection.sendall(data)
    else:
        # Interpret empty result as closed connection
        print('  closing')
        mysel.unregister(connection)
        connection.close()
        # Tell the main loop to stop
        keep_running = False


def accept(sock, mask):
    "Callback for new connections"
    new_connection, addr = sock.accept()
    print('accept({})'.format(addr))
    new_connection.setblocking(False)
    mysel.register(new_connection, selectors.EVENT_READ, read)


server_address = ('localhost', 10000)
print('starting up on {} port {}'.format(*server_address))
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
server.bind(server_address)
server.listen(5)

mysel.register(server, selectors.EVENT_READ, accept)

while keep_running:
    print('waiting for I/O')
    for key, mask in mysel.select(timeout=1):
        callback = key.data
        callback(key.fileobj, mask)

print('shutting down')
mysel.close()

Cuando read() no recibe datos del conector, interpreta el evento de lectura como que el otro lado de la conexión que se está cerrando en lugar de estar enviando datos. Se quita el conector del selector y se cierra. eso. Para evitar un bucle infinito, este servidor también se cierra después de que haya terminado de comunicarse con un cliente.

Cliente de eco

El siguiente ejemplo del cliente de eco procesa todos los eventos de E/S en el bucle principal, en lugar de utilizar devoluciones de llamada. Se configura el selector para que informe los eventos de lectura en el conector, y para que informe cuando el conector está listo para enviar datos. Debido a que se trata de dos tipos de eventos, el el cliente debe verificar lo que ocurrió examinando el valor de la máscara. Después de que todos sus datos salientes han sido enviados, cambia la configuración del selector. para reportar solo cuando hay datos para leer.

selectors_echo_client.py
import selectors
import socket

mysel = selectors.DefaultSelector()
keep_running = True
outgoing = [
    b'It will be repeated.',
    b'This is the message.  ',
]
bytes_sent = 0
bytes_received = 0

# Connecting is a blocking operation, so call setblocking()
# after it returns.
server_address = ('localhost', 10000)
print('connecting to {} port {}'.format(*server_address))
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(server_address)
sock.setblocking(False)

# Set up the selector to watch for when the socket is ready
# to send data as well as when there is data to read.
mysel.register(
    sock,
    selectors.EVENT_READ | selectors.EVENT_WRITE,
)

while keep_running:
    print('waiting for I/O')
    for key, mask in mysel.select(timeout=1):
        connection = key.fileobj
        client_address = connection.getpeername()
        print('client({})'.format(client_address))

        if mask & selectors.EVENT_READ:
            print('  ready to read')
            data = connection.recv(1024)
            if data:
                # A readable client socket has data
                print('  received {!r}'.format(data))
                bytes_received += len(data)

            # Interpret empty result as closed connection,
            # and also close when we have received a copy
            # of all of the data sent.
            keep_running = not (
                data or
                (bytes_received and
                 (bytes_received == bytes_sent))
            )

        if mask & selectors.EVENT_WRITE:
            print('  ready to write')
            if not outgoing:
                # We are out of messages, so we no longer need to
                # write anything. Change our registration to let
                # us keep reading responses from the server.
                print('  switching to read-only')
                mysel.modify(sock, selectors.EVENT_READ)
            else:
                # Send the next message.
                next_msg = outgoing.pop()
                print('  sending {!r}'.format(next_msg))
                sock.sendall(next_msg)
                bytes_sent += len(next_msg)

print('shutting down')
mysel.unregister(connection)
connection.close()
mysel.close()

El cliente sigue la cantidad de datos que ha enviado y la cantidad que ha recibido. Cuando esos valores coinciden y son distintos de cero, el cliente sale del bucle de procesamiento y se cierra limpiamente eliminando el conector desde el selector y cerrando tanto el conector como el selector.

Servidor y cliente juntos

El cliente y el servidor deben ejecutarse en ventanas de terminal separadas, para que puedan comunicarse entre ellos. La salida del servidor muestra la conexión y datos entrantes, así como la respuesta enviada al cliente.

$ python3 source/selectors/selectors_echo_server.py
starting up on localhost port 10000
waiting for I/O
waiting for I/O
accept(('127.0.0.1', 59850))
waiting for I/O
read(('127.0.0.1', 59850))
  received b'This is the message.  It will be repeated.'
waiting for I/O
read(('127.0.0.1', 59850))
  closing
shutting down

La salida del cliente muestra el mensaje saliente y la respuesta del servidor.

$ python3 source/selectors/selectors_echo_client.py
connecting to localhost port 10000
waiting for I/O
client(('127.0.0.1', 10000))
  ready to write
  sending b'This is the message.  '
waiting for I/O
client(('127.0.0.1', 10000))
  ready to write
  sending b'It will be repeated.'
waiting for I/O
client(('127.0.0.1', 10000))
  ready to write
  switching to read-only
waiting for I/O
client(('127.0.0.1', 10000))
  ready to read
  received b'This is the message.  It will be repeated.'
shutting down

Ver también