Transportes y protocolos¶
Prefacio
Los transportes y protocolos son utilizados por las APIs de bajo nivel de los bucles de eventos, como loop.create_connection()
. Utilizan un estilo de programación basado en retrollamadas y permiten implementaciones de alto rendimiento de protocolos de red o IPC (p. ej. HTTP).
Esencialmente, los transportes y protocolos solo deben usarse en bibliotecas y frameworks, nunca en aplicaciones asyncio de alto nivel.
Esta página de la documentación cubre tanto Transports como Protocols.
Introducción
En el nivel más alto, el transporte se ocupa de cómo se transmiten los bytes, mientras que el protocolo determina qué bytes transmitir (y hasta cierto punto cuándo).
Una forma diferente de decir lo mismo: Un transporte es una abstracción para un socket (o un punto final de E/S similar) mientras que un protocolo es una abstracción para una aplicación, desde el punto de vista del transporte.
Otro punto de vista más es que las interfaces de transporte y protocolo definen juntas una interfaz abstracta para usar E/S de red y E/S entre procesos.
Siempre existe una relación 1:1 entre el transporte y los objetos protocolo: el protocolo llama a los métodos del transporte para enviar datos, mientras que el transporte llama a los métodos del protocolo para enviarle los datos que se han recibido.
La mayoría de los métodos del bucle de eventos orientados a la conexión (como loop.create_connection()
) aceptan generalmente un argumento protocol_factory que es usado para crear un objeto Protocol para una conexión aceptada, representada por un objeto Transport. Estos métodos suelen retornar una tupla de la forma (transporte, protocolo)
.
Contenidos
Esta página de la documentación contiene las siguientes secciones:
La sección Transports documenta las clases asyncio
BaseTransport
,ReadTransport
,WriteTransport
,Transport
,DatagramTransport
ySubprocessTransport
.La sección Protocols documenta las clases asyncio
BaseProtocol
,Protocol
,BufferedProtocol
,DatagramProtocol
ySubprocessProtocol
.La sección Examples muestra cómo trabajar con transportes, protocolos y las APIs de bajo nivel del bucle de eventos.
Transportes¶
Código fuente: Lib/asyncio/transports.py
Los transportes son clases proporcionadas por asyncio
para abstraer varios tipos de canales de comunicación.
Los objetos transporte siempre son instanciados por un bucle de eventos asyncio.
asyncio implementa transportes para TCP, UDP, SSL y pipes de subprocesos. Los métodos disponibles en un transporte dependen del tipo de transporte.
Las clases transporte no son seguras en hilos.
Jerarquía de transportes¶
- class asyncio.BaseTransport¶
Clase base para todos los transportes. Contiene métodos que todos los transportes asyncio comparten.
- class asyncio.WriteTransport(BaseTransport)¶
Un transporte base para conexiones de solo escritura.
Las instancias de la clase WriteTransport se retornan desde el método del bucle de eventos
loop.connect_write_pipe()
y también se utilizan en métodos relacionados con subprocesos comoloop.subprocess_exec()
.
- class asyncio.ReadTransport(BaseTransport)¶
Un transporte base para conexiones de solo lectura.
Las instancias de la clase ReadTransport se retornan desde el método del bucle de eventos
loop.connect_read_pipe()
y también se utilizan en métodos relacionados con subprocesos comoloop.subprocess_exec()
.
- class asyncio.Transport(WriteTransport, ReadTransport)¶
Interfaz que representa un transporte bidireccional, como una conexión TCP.
El usuario no crea una instancia de transporte directamente; en su lugar se llama a una función de utilidad, pasándole una fábrica de protocolos junto a otra información necesaria para crear el transporte y el protocolo.
Las instancias de la clase Transport son retornadas o utilizadas por métodos del bucle de eventos como
loop.create_connection()
,loop.create_unix_connection()
,loop.create_server()
,loop.sendfile()
, etc.
- class asyncio.DatagramTransport(BaseTransport)¶
Un transporte para conexiones de datagramas (UDP).
Las instancias de la clase DatagramTransport se retornan desde el método
loop.create_datagram_endpoint()
del bucle de eventos.
- class asyncio.SubprocessTransport(BaseTransport)¶
Una abstracción para representar una conexión entre un proceso padre y su proceso OS hijo.
Las instancias de la clase SubprocessTransport se retornan desde los métodos del bucle de eventos
loop.subprocess_shell()
yloop.subprocess_exec()
.
Transporte base¶
- BaseTransport.close()¶
Cierra el transporte.
Si el transporte tiene un búfer para datos de salida, los datos almacenados en el búfer se eliminarán de forma asincrónica. No se recibirán más datos. Después de que se vacíen todos los datos almacenados en el búfer, el método
protocol.connection_lost()
del protocolo se llamará conNone
como argumento. El transporte no debería ser usado luego de haber sido cerrado.
- BaseTransport.is_closing()¶
Retorna
True
si el transporte se está cerrando o está ya cerrado.
- BaseTransport.get_extra_info(name, default=None)¶
Retorna información sobre el transporte o los recursos subyacentes que utiliza.
name es una cadena de caracteres que representa la información específica del transporte que se va a obtener.
default es el valor que se retornará si la información no está disponible o si el transporte no admite la consulta con la implementación del bucle de eventos de terceros dada o en la plataforma actual.
Por ejemplo, el siguiente código intenta obtener el objeto socket subyacente del transporte:
sock = transport.get_extra_info('socket') if sock is not None: print(sock.getsockopt(...))
Categorías de información que se pueden consultar sobre algunos transportes:
socket:
'peername'
: la dirección remota a la que está conectado el socket, resultado desocket.socket.getpeername()
(None
en caso de error)'socket'
: instancia desocket.socket
'sockname'
: la dirección propia del socket, resultado desocket.socket.getsockname()
socket SSL:
'compression'
: el algoritmo de compresión que se está usando como una cadena de caracteres oNone
si la conexión no está comprimida; resultado dessl.SSLSocket.compression()
'cipher'
: una tupla de tres valores que contiene el nombre del cifrado que se está utilizando, la versión del protocolo SSL que define su uso y la cantidad de bits de la clave secreta que se utilizan; resultado dessl.SSLSocket.cipher()
'peercert'
: certificado de pares; resultado dessl.SSLSocket.getpeercert()
'sslcontext'
: instancia dessl.SSLContext
'ssl_object'
: instancia dessl.SSLObject
ossl.SSLSocket
pipe:
'pipe'
: objeto pipe
subproceso:
'subprocess'
: instancia desubprocess.Popen
- BaseTransport.set_protocol(protocol)¶
Establece un nuevo protocolo.
El cambio de protocolo solo debe realizarse cuando esté documentado que ambos protocolos admiten el cambio.
- BaseTransport.get_protocol()¶
Retorna el protocolo actual.
Transportes de solo lectura¶
- ReadTransport.is_reading()¶
Retorna
True
si el transporte está recibiendo nuevos datos.Added in version 3.7.
- ReadTransport.pause_reading()¶
Pausa el extremo receptor del transporte. No se pasarán datos al método
protocol.data_received()
del protocolo hasta que se llame aresume_reading()
.Distinto en la versión 3.7: El método es idempotente, es decir, se puede llamar cuando el transporte ya está en pausa o cerrado.
- ReadTransport.resume_reading()¶
Reanuda el extremo receptor. El método
protocol.data_received()
del protocolo se llamará una vez más si hay algunos datos disponibles para su lectura.Distinto en la versión 3.7: El método es idempotente, es decir, se puede llamar cuando el transporte está leyendo.
Transportes de solo escritura¶
- WriteTransport.abort()¶
Cierra el transporte inmediatamente, sin esperar a que finalicen las operaciones pendientes. Se perderán los datos almacenados en el búfer. No se recibirán más datos. El método
protocol.connection_lost()
del protocolo será llamado eventualmente conNone
como argumento.
- WriteTransport.can_write_eof()¶
Retorna
True
si el transporte admitewrite_eof()
, en caso contrarioFalse
.
- WriteTransport.get_write_buffer_size()¶
Retorna el tamaño actual del búfer de salida utilizado por el transporte.
- WriteTransport.get_write_buffer_limits()¶
Obtiene los límites superior e inferior para el control del flujo de escritura. Retorna una tupla
(low, high)
donde low (“inferior”) y high (“superior”) son un número de bytes positivo.Usa
set_write_buffer_limits()
para establecer los límites.Added in version 3.4.2.
- WriteTransport.set_write_buffer_limits(high=None, low=None)¶
Establece los límites high (“superior”) y low (“inferior”) para el control del flujo de escritura.
Estos dos valores (medidos en número de bytes) controlan cuándo se llaman los métodos
protocol.pause_writing()
yprotocol.resume_writing()
del protocolo . Si se especifica, el límite inferior debe ser menor o igual que el límite superior. Ni high ni low pueden ser negativos.pause_writing()
se llama cuando el tamaño del búfer es mayor o igual que el valor high (“superior”). Si se ha pausado la escritura, se llama aresume_writing()
cuando el tamaño del búfer es menor o igual que el valor low (“inferior”).Los valores por defecto son específicos de la implementación. Si solo se proporciona el límite superior, el inferior toma de forma predeterminada un valor específico, dependiente de la implementación, menor o igual que el límite superior. Establecer high (“superior”) en cero fuerza low (“inferior”) a cero también y hace que
pause_writing()
sea llamado siempre que el búfer no esté vacío. Establecer low (“inferior”) en cero hace queresume_writing()
sea llamado únicamente cuando el búfer esté vacío. El uso de cero para cualquiera de los límites es generalmente subóptimo, ya que reduce las oportunidades para realizar E/S y cálculos simultáneamente.Usa
get_write_buffer_limits()
para obtener los límites.
- WriteTransport.write(data)¶
Escribe los bytes de data en el transporte.
Este método no bloquea; almacena los datos en el búfer y organiza que se envíen de forma asincrónica.
- WriteTransport.writelines(list_of_data)¶
Escribe una lista (o cualquier iterable) de bytes de datos en el transporte. Esto es funcionalmente equivalente a llamar a
write()
en cada elemento generado por el iterable, pero puede ser implementado de manera más eficiente.
- WriteTransport.write_eof()¶
Cierra el extremo de escritura del transporte después de vaciar todos los datos almacenados en el búfer. Aún es posible recibir datos.
Este método puede lanzar una excepción
NotImplementedError
si el transporte (p. ej. SSL) no soporta conexiones semicerradas (half-closed).
Transportes de datagramas¶
- DatagramTransport.sendto(data, addr=None)¶
Envía los bytes data al par remoto proporcionado por addr (una dirección de destino dependiente del transporte). Si addr es
None
, los datos se envían a la dirección de destino proporcionada en la creación del transporte.Este método no bloquea; almacena los datos en el búfer y organiza que se envíen de forma asincrónica.
Distinto en la versión 3.13: This method can be called with an empty bytes object to send a zero-length datagram. The buffer size calculation used for flow control is also updated to account for the datagram header.
- DatagramTransport.abort()¶
Cierra el transporte inmediatamente, sin esperar a que finalicen las operaciones pendientes. Se perderán los datos almacenados en el búfer. No se recibirán más datos. El método
protocol.connection_lost()
del protocolo será llamado eventualmente conNone
como argumento.
Transportes de subprocesos¶
- SubprocessTransport.get_pid()¶
Retorna la id del subproceso como un número entero.
- SubprocessTransport.get_pipe_transport(fd)¶
Retorna el transporte para la pipe de comunicación correspondiente al descriptor de archivo entero fd:
0
: transporte de streaming para lectura de la entrada estándar (stdin) oNone
si el subproceso no se creó constdin = PIPE
1
: transporte de streaming para escritura de la salida estándar (stdout) oNone
si el subproceso no se creó constdout = PIPE
2
: transporte de streaming para escritura del error estándar (stderr) oNone
si el subproceso no se creó constderr = PIPE
otro fd:
None
- SubprocessTransport.get_returncode()¶
Retorna el código de retorno del subproceso como un entero o
None
si no ha retornado aún, lo que es similar al atributosubprocess.Popen.returncode
.
- SubprocessTransport.kill()¶
Mata al subproceso.
En los sistemas POSIX, la función envía SIGKILL al subproceso. En Windows, este método es un alias para
terminate()
.Ver también
subprocess.Popen.kill()
.
- SubprocessTransport.send_signal(signal)¶
Envía el número de señal al subproceso, como en
subprocess.Popen.send_signal()
.
- SubprocessTransport.terminate()¶
Detiene el subproceso.
On POSIX systems, this method sends
SIGTERM
to the subprocess. On Windows, the Windows API functionTerminateProcess()
is called to stop the subprocess.Ver también
subprocess.Popen.terminate()
.
Protocolos¶
Código fuente: Lib/asyncio/protocols.py
asyncio proporciona un conjunto de clases base abstractas que pueden usarse para implementar protocolos de red. Estas clases están destinadas a ser utilizadas junto con los transportes.
Las subclases de las clases abstractas de protocolos base pueden implementar algunos o todos los métodos. Todos estos métodos son retrollamadas: son llamados por los transportes en ciertos eventos, por ejemplo, cuando se reciben algunos datos. Un método del protocolo base debe ser llamado por el transporte correspondiente.
Protocolos base¶
- class asyncio.BaseProtocol¶
Protocolo base con métodos que comparten todos los demás protocolos.
- class asyncio.Protocol(BaseProtocol)¶
La clase base para implementar protocolos de streaming (TCP, sockets Unix, etc).
- class asyncio.BufferedProtocol(BaseProtocol)¶
Una clase base para implementar protocolos de streaming con control manual del búfer de recepción.
- class asyncio.DatagramProtocol(BaseProtocol)¶
La clase base para implementar protocolos de datagramas (UDP).
- class asyncio.SubprocessProtocol(BaseProtocol)¶
La clase base para implementar protocolos que se comunican con procesos secundarios (pipes unidireccionales).
Protocolo base¶
Todos los protocolos asyncio pueden implementar las retrollamadas del protocolo base.
Retrollamadas de conexión
Las retrollamadas de conexión son llamadas exactamente una vez por conexión establecida en todos los protocolos. Todas las demás retrollamadas del protocolo solo pueden ser llamadas entre estos dos métodos.
- BaseProtocol.connection_made(transport)¶
Se llama cuando se establece una conexión.
El argumento transport es el transporte que representa la conexión. El protocolo se encarga de almacenar la referencia a su propio transporte.
- BaseProtocol.connection_lost(exc)¶
Se llama cuando la conexión se pierde o se cierra.
El argumento es un objeto excepción o
None
. Esto último significa que se recibió un EOF regular o que la conexión fue cancelada o cerrada por este lado de la conexión.
Retrollamadas de control de flujo
Los transportes pueden llamar a las retrollamadas de control de flujo para pausar o reanudar la escritura llevada a cabo por el protocolo.
Consulta la documentación del método set_write_buffer_limits()
para obtener más detalles.
- BaseProtocol.pause_writing()¶
Se llama cuando el búfer del transporte supera el límite superior.
- BaseProtocol.resume_writing()¶
Se llama cuando el búfer del transporte se vacía por debajo del límite inferior.
Si el tamaño del búfer es igual al límite superior, pause_writing()
no será llamado: el tamaño del búfer debe superarse estrictamente.
Por el contrario, se llama a resume_writing()
cuando el tamaño del búfer es igual o menor que el límite inferior. Estas condiciones finales son importantes para garantizar que todo salga como se espera cuando cualquiera de los dos límites sea cero.
Protocolos de streaming¶
Los métodos de eventos, como loop.create_server()
, loop.create_unix_server()
, loop.create_connection()
, loop.create_unix_connection()
, loop.connect_accepted_socket()
, loop.connect_read_pipe()
, y loop.connect_write_pipe()
aceptan fábricas que retornan protocolos de streaming.
- Protocol.data_received(data)¶
Se llama cuando se reciben algunos datos. data es un objeto bytes no vacío que contiene los datos entrantes.
Que los datos se almacenen en un búfer, que se fragmenten o se vuelvan a ensamblar depende del transporte. En general, no debe confiar en semánticas específicas y, en cambio, hacer que su análisis sea genérico y flexible. Sin embargo, los datos siempre se reciben en el orden correcto.
El método se puede llamar un número arbitrario de veces mientras una conexión esté abierta.
Sin embargo,
protocol.eof_received()
se llama como máximo una vez. Luego de llamar aeof_received()
, ya no se llama más adata_received()
.
- Protocol.eof_received()¶
Se llama cuando el otro extremo indica que no enviará más datos (por ejemplo, llamando a
transport.write_eof()
si el otro extremo también usa asyncio).Este método puede retornar un valor falso (incluido
None
), en cuyo caso el transporte se cerrará solo. Por el contrario, si este método retorna un valor verdadero, el protocolo utilizado determina si se debe cerrar el transporte. Dado que la implementación por defecto retornaNone
, en éste caso, se cierra implícitamente la conexión.Algunos transportes, incluido SSL, no admiten conexiones semicerradas (half-closed), en cuyo caso retornar verdadero desde este método resultará en el cierre de la conexión.
Máquina de estado:
start -> connection_made
[-> data_received]*
[-> eof_received]?
-> connection_lost -> end
Protocolos de streaming mediante búfer¶
Added in version 3.7.
Los protocolos que hacen uso de un búfer se pueden utilizar con cualquier método del bucle de eventos que admita Streaming Protocols.
Las implementaciones de BufferedProtocol
permiten la asignación manual explícita y el control del búfer de recepción. Los bucles de eventos pueden utilizar el búfer proporcionado por el protocolo para evitar copias de datos innecesarias. Esto puede resultar en una mejora notable del rendimiento de los protocolos que reciben grandes cantidades de datos. Las implementaciones de protocolos sofisticados pueden reducir significativamente la cantidad de asignaciones de búfer.
Las siguientes retrollamadas son llamadas en instancias BufferedProtocol
:
- BufferedProtocol.get_buffer(sizehint)¶
Se llama para asignar un nuevo búfer de recepción.
sizehint es el tamaño mínimo recomendado para el búfer retornado. Es aceptable retornar búferes más pequeños o más grandes de lo que sugiere sizehint. Cuando se establece en -1, el tamaño del búfer puede ser arbitrario. Es un error retornar un búfer con tamaño cero.
get_buffer()
debe retornar un objeto que implemente el protocolo de búfer.
- BufferedProtocol.buffer_updated(nbytes)¶
Se llama cuando el búfer se ha actualizado con los datos recibidos.
nbytes es el número total de bytes que se escribieron en el búfer.
- BufferedProtocol.eof_received()¶
Consulte la documentación del método
protocol.eof_received()
.
get_buffer()
se puede llamar un número arbitrario de veces durante una conexión. Sin embargo, protocol.eof_received ()
se llama como máximo una vez y, si se llama, get_buffer()
y buffer_updated()
no serán llamados después de eso.
Máquina de estado:
start -> connection_made
[-> get_buffer
[-> buffer_updated]?
]*
[-> eof_received]?
-> connection_lost -> end
Protocolos de datagramas¶
Las instancias del protocolo de datagramas deben ser construidas por fábricas de protocolos pasadas al método loop.create_datagram_endpoint()
.
- DatagramProtocol.datagram_received(data, addr)¶
Se llama cuando se recibe un datagrama. data es un objeto bytes que contiene los datos entrantes. addr es la dirección del par que envía los datos; el formato exacto depende del transporte.
- DatagramProtocol.error_received(exc)¶
Se llama cuando una operación de envío o recepción anterior genera una
OSError
. exc es la instanciaOSError
.Este método se llama en condiciones excepcionales, cuando el transporte (por ejemplo, UDP) detecta que un datagrama no se pudo entregar a su destinatario. Sin embargo, en la mayoría de casos, los datagramas que no se puedan entregar se eliminarán silenciosamente.
Nota
En los sistemas BSD (macOS, FreeBSD, etc.) el control de flujo no es compatible con los protocolos de datagramas, esto se debe a que no hay una forma confiable de detectar fallos de envío causados por escribir demasiados paquetes.
El socket siempre aparece como disponible (“ready”) y se eliminan los paquetes sobrantes. Un error OSError
con errno
establecido en errno.ENOBUFS
puede o no ser generado; si se genera, se informará a DatagramProtocol.error_received()
pero en caso contrario se ignorará.
Protocolos de subprocesos¶
Las instancias de protocolo de subproceso deben ser construidas por fábricas de protocolos pasadas a los métodos loop.subprocess_exec()
y loop.subprocess_shell()
.
- SubprocessProtocol.pipe_data_received(fd, data)¶
Se llama cuando el proceso hijo escribe datos en su pipe stdout o stderr.
fd es el descriptor de archivo entero de la pipe.
data es un objeto bytes no vacío que contiene los datos recibidos.
- SubprocessProtocol.pipe_connection_lost(fd, exc)¶
Se llama cuando se cierra una de las pipes que se comunican con el proceso hijo.
fd es el descriptor de archivo entero que se cerró.
- SubprocessProtocol.process_exited()¶
Se llama cuando el proceso hijo ha finalizado.
It can be called before
pipe_data_received()
andpipe_connection_lost()
methods.
Ejemplos¶
Servidor de eco TCP¶
Crear un servidor de eco TCP usando el método loop.create_server()
, enviar de vuelta los datos recibidos y cerrar la conexión:
import asyncio
class EchoServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
peername = transport.get_extra_info('peername')
print('Connection from {}'.format(peername))
self.transport = transport
def data_received(self, data):
message = data.decode()
print('Data received: {!r}'.format(message))
print('Send: {!r}'.format(message))
self.transport.write(data)
print('Close the client socket')
self.transport.close()
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
server = await loop.create_server(
EchoServerProtocol,
'127.0.0.1', 8888)
async with server:
await server.serve_forever()
asyncio.run(main())
Ver también
El ejemplo Servidor de eco TCP usando streams usa la función de alto nivel asyncio.start_server()
.
Cliente de eco TCP¶
Un cliente de eco TCP usando el método loop.create_connection()
, envía datos y espera hasta que la conexión se cierre:
import asyncio
class EchoClientProtocol(asyncio.Protocol):
def __init__(self, message, on_con_lost):
self.message = message
self.on_con_lost = on_con_lost
def connection_made(self, transport):
transport.write(self.message.encode())
print('Data sent: {!r}'.format(self.message))
def data_received(self, data):
print('Data received: {!r}'.format(data.decode()))
def connection_lost(self, exc):
print('The server closed the connection')
self.on_con_lost.set_result(True)
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
on_con_lost = loop.create_future()
message = 'Hello World!'
transport, protocol = await loop.create_connection(
lambda: EchoClientProtocol(message, on_con_lost),
'127.0.0.1', 8888)
# Wait until the protocol signals that the connection
# is lost and close the transport.
try:
await on_con_lost
finally:
transport.close()
asyncio.run(main())
Ver también
El ejemplo Cliente de eco TCP usando streams usa la función de alto nivel asyncio.open_connection()
.
Servidor de eco UDP¶
Un servidor de eco UDP, usando el método loop.create_datagram_endpoint()
, envía de vuelta los datos recibidos:
import asyncio
class EchoServerProtocol:
def connection_made(self, transport):
self.transport = transport
def datagram_received(self, data, addr):
message = data.decode()
print('Received %r from %s' % (message, addr))
print('Send %r to %s' % (message, addr))
self.transport.sendto(data, addr)
async def main():
print("Starting UDP server")
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
# One protocol instance will be created to serve all
# client requests.
transport, protocol = await loop.create_datagram_endpoint(
EchoServerProtocol,
local_addr=('127.0.0.1', 9999))
try:
await asyncio.sleep(3600) # Serve for 1 hour.
finally:
transport.close()
asyncio.run(main())
Cliente de eco UDP¶
Un cliente de eco UDP, usando el método loop.create_datagram_endpoint()
, envía datos y cierra el transporte cuando recibe la respuesta:
import asyncio
class EchoClientProtocol:
def __init__(self, message, on_con_lost):
self.message = message
self.on_con_lost = on_con_lost
self.transport = None
def connection_made(self, transport):
self.transport = transport
print('Send:', self.message)
self.transport.sendto(self.message.encode())
def datagram_received(self, data, addr):
print("Received:", data.decode())
print("Close the socket")
self.transport.close()
def error_received(self, exc):
print('Error received:', exc)
def connection_lost(self, exc):
print("Connection closed")
self.on_con_lost.set_result(True)
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
on_con_lost = loop.create_future()
message = "Hello World!"
transport, protocol = await loop.create_datagram_endpoint(
lambda: EchoClientProtocol(message, on_con_lost),
remote_addr=('127.0.0.1', 9999))
try:
await on_con_lost
finally:
transport.close()
asyncio.run(main())
Conectando sockets existentes¶
Espera hasta que un socket reciba datos usando el método loop.create_connection()
mediante un protocolo:
import asyncio
import socket
class MyProtocol(asyncio.Protocol):
def __init__(self, on_con_lost):
self.transport = None
self.on_con_lost = on_con_lost
def connection_made(self, transport):
self.transport = transport
def data_received(self, data):
print("Received:", data.decode())
# We are done: close the transport;
# connection_lost() will be called automatically.
self.transport.close()
def connection_lost(self, exc):
# The socket has been closed
self.on_con_lost.set_result(True)
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
on_con_lost = loop.create_future()
# Create a pair of connected sockets
rsock, wsock = socket.socketpair()
# Register the socket to wait for data.
transport, protocol = await loop.create_connection(
lambda: MyProtocol(on_con_lost), sock=rsock)
# Simulate the reception of data from the network.
loop.call_soon(wsock.send, 'abc'.encode())
try:
await protocol.on_con_lost
finally:
transport.close()
wsock.close()
asyncio.run(main())
Ver también
El ejemplo monitorizar eventos de lectura en un descriptor de archivo utiliza el método de bajo nivel loop.add_reader()
para registrar un descriptor de archivo.
El ejemplo registrar un socket abierto a la espera de datos usando streams usa streams de alto nivel creados por la función open_connection()
en una corrutina.
loop.subprocess_exec() y SubprocessProtocol¶
Un ejemplo de un protocolo de subproceso que se utiliza para obtener la salida de un subproceso y esperar su terminación.
El subproceso es creado por el método loop.subprocess_exec()
import asyncio
import sys
class DateProtocol(asyncio.SubprocessProtocol):
def __init__(self, exit_future):
self.exit_future = exit_future
self.output = bytearray()
self.pipe_closed = False
self.exited = False
def pipe_connection_lost(self, fd, exc):
self.pipe_closed = True
self.check_for_exit()
def pipe_data_received(self, fd, data):
self.output.extend(data)
def process_exited(self):
self.exited = True
# process_exited() method can be called before
# pipe_connection_lost() method: wait until both methods are
# called.
self.check_for_exit()
def check_for_exit(self):
if self.pipe_closed and self.exited:
self.exit_future.set_result(True)
async def get_date():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
code = 'import datetime; print(datetime.datetime.now())'
exit_future = asyncio.Future(loop=loop)
# Create the subprocess controlled by DateProtocol;
# redirect the standard output into a pipe.
transport, protocol = await loop.subprocess_exec(
lambda: DateProtocol(exit_future),
sys.executable, '-c', code,
stdin=None, stderr=None)
# Wait for the subprocess exit using the process_exited()
# method of the protocol.
await exit_future
# Close the stdout pipe.
transport.close()
# Read the output which was collected by the
# pipe_data_received() method of the protocol.
data = bytes(protocol.output)
return data.decode('ascii').rstrip()
date = asyncio.run(get_date())
print(f"Current date: {date}")
Consulte también el mismo ejemplo escrito utilizando la API de alto nivel.