# Transports et Protocoles

Préface

Les transports et les protocoles sont utilisés par les API de boucle d'événements de bas niveau telles que `loop.create_connection()`. Ils utilisent un style de programmation basé sur les fonctions de rappel et permettent des implémentations hautes performances de protocoles réseau (par exemple, HTTP) ou de communication inter-processus (IPC).

Avant tout, les transports et les protocoles ne doivent être utilisés que dans des bibliothèques et des cadriciels et jamais dans des applications asynchrones de haut niveau.

Cette page de documentation couvre à la fois Transports et Protocoles.

Introduction

Au plus haut niveau, le transport concerne comment les octets sont transmis, tandis que le protocole détermine quels octets transmettre (et dans une certaine mesure quand).

Pour l'écrire autrement : un transport est une abstraction pour un connecteur (socket ou tout autre point de terminaison d'entrée-sortie) tandis qu'un protocole est une abstraction pour une application, du point de vue du transport.

Encore une autre vue est que les interfaces de transport et de protocole définissent ensemble une interface abstraite pour utiliser les entrées-sorties réseau et les entrées-sorties inter-processus.

Il existe toujours une relation 1:1 entre les objets de transport et de protocole : le protocole appelle les méthodes de transport pour envoyer des données, tandis que le transport appelle les méthodes de protocole pour lui transmettre les données reçues.

La plupart des méthodes de boucles d'événements orientées connexion (telles que `loop.create_connection()`) acceptent généralement un argument protocol_factory utilisé pour créer un objet Protocol pour une connexion acceptée, représentée par un objet Transport. De telles méthodes renvoient généralement un n-uplet `(transport, protocol)`.

Sommaire

Cette page de documentation contient les sections suivantes :

- La section Transports documente les classes asyncio `BaseTransport`, `ReadTransport`, `WriteTransport`, `Transport`, `DatagramTransport` et `SubprocessTransport`.

- La section Protocols documente les classes asyncio `BaseProtocol`, `Protocol`, `BufferedProtocol`, `DatagramProtocol` et `SubprocessProtocol`.

- La section Examples montre comment utiliser les transports, les protocoles et les API de boucle d'événements de bas niveau.

## Transports

Code source : Lib/asyncio/transports.py

Les transports sont des classes fournies par `asyncio` afin d'abstraire différents types de canaux de communication.

Les objets de transport sont toujours instanciés par une boucle d'événements asyncio.

asyncio implémente les transports pour TCP, UDP, SSL et les tubes de sous-processus. Les méthodes disponibles sur un transport dépendent du type de transport.

Les classes de transport ne sont pas compatibles avec les fils d'exécution multiples.

### Hiérarchie des transports

class asyncio.BaseTransport

  Classe de base pour tous les transports. Contient des méthodes partagées par tous les transports asyncio.

class asyncio.WriteTransport(BaseTransport)

  Transport de base pour les connexions en écriture seule.

  Les instances de la classe WriteTransport sont renvoyées par la méthode de boucle d'événements `loop.connect_write_pipe()` et sont également utilisées par les méthodes liées aux sous-processus comme `loop.subprocess_exec()`.

class asyncio.ReadTransport(BaseTransport)

  Transport de base pour les connexions en lecture seule.

  Les instances de la classe ReadTransport sont renvoyées par la méthode de boucle d'événements `loop.connect_read_pipe()` et sont également utilisées par les méthodes liées aux sous-processus comme `loop.subprocess_exec()`.

class asyncio.Transport(WriteTransport, ReadTransport)

  Interface représentant un transport bidirectionnel, comme une connexion TCP.

  L'utilisateur n'instancie pas un transport directement ; il appelle une fonction utilitaire, lui transmettant une fabrique de protocoles et d'autres informations nécessaires pour créer le transport et le protocole.

  Les instances de la classe Transport sont renvoyées ou utilisées par des méthodes de boucle d'événements comme `loop.create_connection()`, `loop.create_unix_connection()`, `loop.create_server()`, `loop.sendfile()`, etc.

class asyncio.DatagramTransport(BaseTransport)

  Transport pour les connexions par datagrammes (UDP).

  Les instances de la classe DatagramTransport sont renvoyées par la méthode de boucle d'événements `loop.create_datagram_endpoint()`.

class asyncio.SubprocessTransport(BaseTransport)

  Abstraction pour représenter une connexion entre un parent et son processus enfant au niveau du système d'exploitation.

  Les instances de la classe SubprocessTransport sont renvoyées par les méthodes de boucle d'événements `loop.subprocess_shell()` et `loop.subprocess_exec()`.

### Classe de base des Transports

BaseTransport.close()

  Ferme le transport.

  Si le transport a une mémoire tampon pour les données sortantes, les données mises en mémoire tampon seront vidées de manière asynchrone. Aucune autre donnée ne sera reçue. Une fois que toutes les données mises en mémoire tampon ont été vidées, la méthode `protocol.connection_lost()` sera appelée avec `None` comme argument. Le transport ne doit pas être utilisé une fois qu'il est fermé.

BaseTransport.is_closing()

  Renvoie `True` si le transport se ferme ou est fermé.

BaseTransport.get_extra_info(name, default=None)

  Renvoie des informations sur le transport ou les ressources sous-jacentes qu'il utilise.

  name est une chaîne représentant l'information spécifique au transport à obtenir.

  default est la valeur à renvoyer si les informations ne sont pas disponibles ou si le transport ne prend pas en charge l'implémentation de boucle d'événements tierce donnée ou la plateforme actuelle.

  Par exemple, le code suivant tente d'obtenir l'objet socket sous-jacent du transport :

      sock = transport.get_extra_info('socket')
      if sock is not None:
          print(sock.getsockopt(...))

  Catégories d'informations pouvant être interrogées sur certains transports :

  - socket :

    - `'peername'` : l'adresse distante à laquelle le socket est connecté, résultat de `socket.socket.getpeername()` (`None` en cas d'erreur)

    - `'socket'` : Instance de `socket.socket`

    - `'sockname'` : la propre adresse du connecteur, résultat de `socket.socket.getsockname()`

  - Connecteur (socket) SSL :

    - `'compression'` : l'algorithme de compression utilisé (chaîne de caractères), ou `None` si la connexion n'est pas compressée ; résultat de `ssl.SSLSocket.compression()`

    - `'cipher'` : un n-uplet à trois valeurs contenant le nom du chiffrement utilisé, la version du protocole SSL qui définit son utilisation et le nombre de bits secrets utilisés ; résultat de `ssl.SSLSocket.cipher()`

    - `'peercert'` : certificat du pair ; résultat de `ssl.SSLSocket.getpeercert()`

    - `sslcontext'` : instance de `ssl.SSLContext`

    - `'ssl_object'` : instance de `ssl.SSLObject` ou de `ssl.SSLSocket`

  - tube :

    - `'pipe'` : objet pipe

  - sous-processus :

    - `'sous-processus'` : instance de `subprocess.Popen`

BaseTransport.set_protocol(protocol)

  Change le protocole.

  La commutation de protocole ne doit être effectuée que lorsque les deux protocoles sont documentés pour prendre en charge la commutation.

BaseTransport.get_protocol()

  Renvoie le protocole courant.

### Transports en lecture seule

ReadTransport.is_reading()

  Renvoie `True` si le transport reçoit de nouvelles données.

  Added in version 3.7.

ReadTransport.pause_reading()

  Met en pause l'extrémité de réception du transport. Aucune donnée ne sera transmise à la méthode `protocol.data_received()` du protocole jusqu'à ce que `resume_reading()` soit appelée.

  Modifié dans la version 3.7: la méthode est idempotente, c'est-à-dire qu'elle peut être appelée lorsque le transport est déjà en pause ou fermé.

ReadTransport.resume_reading()

  Reprend la réception. La méthode `protocol.data_received()` du protocole sera appelée à nouveau si certaines données sont disponibles pour la lecture.

  Modifié dans la version 3.7: la méthode est idempotente, c'est-à-dire qu'elle peut être appelée alors que le transport est déjà en train de lire.

### Transports en lecture-écriture

WriteTransport.abort()

  Ferme le transport immédiatement, sans attendre la fin des opérations en attente. Les données mises en mémoire tampon sont perdues. Aucune autre donnée ne sera reçue. La méthode `protocol.connection_lost()` du protocole sera éventuellement appelée avec `None` comme argument.

WriteTransport.can_write_eof()

  Renvoie `True` si le transport gère `write_eof()`, `False` sinon.

WriteTransport.get_write_buffer_size()

  Renvoie la taille actuelle du tampon de sortie utilisé par le transport.

WriteTransport.get_write_buffer_limits()

  Obtient les seuils high et low pour le contrôle du flux d'écriture. Renvoie un n-uplet `(low, high)` où low et high sont des nombres positifs d'octets.

  Utilisez `set_write_buffer_limits()` pour définir les limites.

  Added in version 3.4.2.

WriteTransport.set_write_buffer_limits(high=None, low=None)

  Définit les seuils high et low pour le contrôle du flux d'écriture.

  Ces deux valeurs (mesurées en nombre d'octets) contrôlent quand les méthodes `protocol.pause_writing()` et `protocol.resume_writing()` du protocole sont appelées. S'il est spécifié, le seuil bas doit être inférieur ou égal au seuil haut. Ni high ni low ne peuvent être négatifs.

  `pause_writing()` est appelée lorsque la taille du tampon devient supérieure ou égale à la valeur high. Si l'écriture a été interrompue, `resume_writing()` est appelée lorsque la taille du tampon devient inférieure ou égale à la valeur low.

  Les valeurs par défaut sont spécifiques à l'implémentation. Si seul le seuil supérieur est donné, le seuil bas prend par défaut une valeur spécifique à l'implémentation inférieure ou égale au seuil supérieur. Définir high sur zéro force également low sur zéro et provoque l'appel de `pause_writing()` chaque fois que le tampon devient non vide. Définir low sur zéro entraîne l'appel de `resume_writing()` uniquement une fois que le tampon est vide. L'utilisation de zéro pour l'un ou l'autre seuil est généralement sous-optimal car cela réduit les possibilités d'effectuer des entrées-sorties et des calculs simultanément.

  Utilisez `get_write_buffer_limits()` pour obtenir les limites.

WriteTransport.write(data)

  Écrit des octets de data sur le transport.

  Cette méthode ne bloque pas ; elle met les données en mémoire tampon et s'arrange pour qu'elles soient envoyées de manière asynchrone.

WriteTransport.writelines(list_of_data)

  Écrit une liste (ou tout itérable) d'octets de données dans le transport. Ceci est fonctionnellement équivalent à appeler `write()` sur chaque élément produit par l'itérable, mais peut être implémentée plus efficacement.

WriteTransport.write_eof()

  Ferme l'extrémité d'écriture du transport après avoir vidé toutes les données mises en mémoire tampon. Des données peuvent encore être reçues.

  Cette méthode peut lever `NotImplementedError` si le transport (par exemple SSL) ne prend pas en charge les connexions semi-fermées.

### Transports par datagrammes

DatagramTransport.sendto(data, addr=None)

  Envoie les octets data au pair distant indiqué par addr (une adresse cible dépendante du transport). Si addr est `None`, les données sont envoyées à l'adresse cible indiquée lors de la création du transport.

  Cette méthode ne bloque pas ; elle met les données en mémoire tampon et s'arrange pour qu'elles soient envoyées de manière asynchrone.

DatagramTransport.abort()

  Ferme le transport immédiatement, sans attendre la fin des opérations en attente. Les données mises en mémoire tampon sont perdues. Aucune autre donnée ne sera reçue. La méthode `protocol.connection_lost()` du protocole sera éventuellement appelée avec `None` comme argument.

### Transports entre sous-processus

SubprocessTransport.get_pid()

  Renvoie l'identifiant du sous processus sous la forme d'un nombre entier.

SubprocessTransport.get_pipe_transport(fd)

  Renvoie le transport pour le canal de communication correspondant au descripteur de fichier fd donné sous forme d'un entier :

  - `0` : transport de flux en lecture de l'entrée standard (stdin), ou `None` si le sous-processus n'a pas été créé avec `stdin=PIPE`

  - `1` : transport de flux en écriture de la sortie standard (stdout), ou `None` si le sous-processus n'a pas été créé avec `stdout=PIPE`

  - `2` : transport de flux en écriture de l'erreur standard (stderr), ou `None` si le sous-processus n'a pas été créé avec `stderr=PIPE`

  - autre fd : `None`

SubprocessTransport.get_returncode()

  Renvoie le code de retour du sous-processus sous la forme d'un entier ou `None` s'il n'a pas été renvoyé, ce qui est similaire à l'attribut `subprocess.Popen.returncode`.

SubprocessTransport.kill()

  Tue le sous-processus.

  Sur les systèmes POSIX, la fonction envoie SIGKILL au sous-processus. Sous Windows, cette méthode est un alias pour `terminate()`.

  Voir aussi `subprocess.Popen.kill()`.

SubprocessTransport.send_signal(signal)

  Envoie le numéro de signal au sous-processus, comme dans `subprocess.Popen.send_signal()`.

SubprocessTransport.terminate()

  Termine le sous-processus.

  On POSIX systems, this method sends `SIGTERM` to the subprocess. On Windows, the Windows API function `TerminateProcess()` is called to stop the subprocess.

  Voir aussi `subprocess.Popen.terminate()`.

SubprocessTransport.close()

  Tue le sous-processus en appelant la méthode `kill()`.

  Si le sous-processus n'est pas encore terminé, ferme les transports des tubes stdin, stdout et stderr.

## Protocoles

Code source : Lib/asyncio/protocols.py

asyncio fournit un ensemble de classes mères abstraites qui doivent être utilisées pour implémenter des protocoles réseau. Ces classes sont destinées à être utilisées avec les transports.

Les sous-classes des classes mères abstraites de protocole peuvent implémenter certaines ou toutes les méthodes. Toutes ces méthodes sont des rappels : elles sont appelées par des transports sur certains événements, par exemple lors de la réception de certaines données. Une méthode de protocole de base doit être appelée par le transport correspondant.

### Protocoles de base

class asyncio.BaseProtocol

  Protocole de base avec des méthodes partagées par tous les protocoles.

class asyncio.Protocol(BaseProtocol)

  Classe mère pour l'implémentation des protocoles de streaming (TCP, sockets Unix, etc.).

class asyncio.BufferedProtocol(BaseProtocol)

  Classe mère pour implémenter des protocoles de streaming avec contrôle manuel du tampon de réception.

class asyncio.DatagramProtocol(BaseProtocol)

  Classe mère pour l'implémentation des protocoles par datagrammes (UDP).

class asyncio.SubprocessProtocol(BaseProtocol)

  Classe mère pour implémenter des protocoles communiquant avec des processus enfants (canaux unidirectionnels).

### Protocoles de base

Tous les protocoles asynchrones peuvent implémenter des rappels pour les protocoles de base.

Rappels pour les connexions

Les méthodes de rappel pour les connexions concernent tous les protocoles, exactement une fois par connexion réussie. Tous les autres rappels de protocole ne peuvent être appelés qu'entre ces deux méthodes.

BaseProtocol.connection_made(transport)

  Appelée lorsqu'une connexion est établie.

  L'argument transport est le transport représentant la connexion. Le protocole est chargé de stocker la référence à son transport.

BaseProtocol.connection_lost(exc)

  Appelée lorsqu'une connexion est perdue ou fermée.

  L'argument est soit un objet exception soit `None`. Ce dernier signifie qu'un EOF régulier est reçu, ou que la connexion a été interrompue ou fermée par ce côté de la connexion.

Rappels pour le contrôle de flux

Les méthodes de rappel pour le contrôle de flux concernent les transports et sont utilisés pour suspendre ou reprendre l'écriture effectuée par le protocole.

Voir la documentation de la méthode `set_write_buffer_limits()` pour plus de détails.

BaseProtocol.pause_writing()

  Appelée lorsque la mémoire tampon du transport dépasse la limite supérieure.

BaseProtocol.resume_writing()

  Appelée lorsque la mémoire tampon du transport passe sous le seuil bas.

Si la taille du tampon est égale au seuil haut, `pause_writing()` n'est pas appelée : la taille du tampon doit être strictement supérieure.

Inversement, `resume_writing()` est appelée lorsque la taille du tampon est égale ou inférieure au seuil bas. Ces conditions de fin sont importantes pour s'assurer que les choses se déroulent comme prévu lorsque l'un ou l'autre seuil est à zéro.

### Protocoles connectés

Les méthodes d'événement, telles que `loop.create_server()`, `loop.create_unix_server()`, `loop.create_connection()`, `loop.create_unix_connection()`, `loop.connect_accepted_socket()`, `loop.connect_read_pipe()` et `loop.connect_write_pipe()` acceptent les fabriques qui renvoient des protocoles connectés.

Protocol.data_received(data)

  Appelée lorsque certaines données sont reçues. data est un objet bytes non vide contenant les données entrantes.

  Le fait que les données soient mises en mémoire tampon, fragmentées ou réassemblées dépend du transport. En général, vous ne devez pas vous fier à une sémantique spécifique et plutôt rendre votre analyse générique et flexible. Cependant, les données sont toujours reçues dans le bon ordre.

  La méthode peut être appelée un nombre arbitraire de fois lorsqu'une connexion est ouverte.

  Cependant, `protocol.eof_received()` est appelée au plus une fois. Une fois que `eof_received()` est appelée, `data_received()` n'est plus appelée.

Protocol.eof_received()

  Appelée lorsque l'autre extrémité signale qu'il n'enverra plus de données (par exemple en appelant `transport.write_eof()`, si l'autre extrémité utilise également asyncio).

  Cette méthode peut renvoyer une valeur évaluée à faux (y compris `None`), auquel cas le transport se ferme de lui-même. À l'inverse, si cette méthode renvoie une valeur évaluée à vrai, le protocole utilisé détermine s'il faut fermer le transport. Puisque l'implémentation par défaut renvoie `None`, elle ferme implicitement la connexion.

  Certains transports, y compris SSL, ne prennent pas en charge les connexions semi-fermées, auquel cas renvoyer True à partir de cette méthode entraîne la fermeture de la connexion.

Machine à états :

    start -> connection_made
        [-> data_received]*
        [-> eof_received]?
    -> connection_lost -> end

### Protocoles connectés avec tampon

Added in version 3.7.

Les protocoles avec mise en mémoire tampon peuvent être utilisés avec n'importe quelle méthode de boucle d'événements prenant en charge les protocoles connectés.

Les implémentations de `BufferedProtocol` permettent une allocation et un contrôle manuels explicites du tampon de réception. Les boucles d'événements peuvent alors utiliser le tampon fourni par le protocole pour éviter les copies de données inutiles. Cela peut entraîner une amélioration notable des performances pour les protocoles qui reçoivent de grandes quantités de données. Des implémentations de protocole sophistiquées peuvent réduire considérablement le nombre d'allocations de mémoire tampon.

Les méthodes de rappel suivantes sont appelées sur les instances `BufferedProtocol` :

BufferedProtocol.get_buffer(sizehint)

  Appelée pour allouer un nouveau tampon de réception.

  sizehint est la taille minimale recommandée pour le tampon renvoyé. Il est acceptable de renvoyer des tampons plus petits ou plus grands que ce que suggère sizehint. Lorsqu'il est défini à −1, la taille du tampon peut être arbitraire. C'est une erreur de renvoyer un tampon de taille nulle.

  `get_buffer()` doit renvoyer un objet implémentant le protocole tampon.

BufferedProtocol.buffer_updated(nbytes)

  Appelée lorsque le tampon a été mis à jour avec les données reçues.

  nbytes est le nombre total d'octets qui ont été écrits dans la mémoire tampon.

BufferedProtocol.eof_received()

  Voir la documentation de la méthode `protocol.eof_received()`.

`get_buffer()` peut être appelée un nombre arbitraire de fois pendant une connexion. Cependant, `protocol.eof_received()` est appelée au plus une fois et, si elle est appelée, `get_buffer()` et `buffer_updated()` ne seront pas appelées après.

Machine à états :

    start -> connection_made
        [-> get_buffer
            [-> buffer_updated]?
        ]*
        [-> eof_received]?
    -> connection_lost -> end

### Protocoles par datagrammes (non connectés)

Les instances du protocole par datagrammes doivent être construites par des fabriques de protocole transmises à la méthode `loop.create_datagram_endpoint()`.

DatagramProtocol.datagram_received(data, addr)

  Appelée lorsqu'un datagramme est reçu. data est un objet bytes contenant les données entrantes. addr est l'adresse du pair qui envoie les données ; le format exact dépend du transport.

DatagramProtocol.error_received(exc)

  Appelée lorsqu'une opération d'envoi ou de réception précédente lève une `OSError`. exc est l'instance `OSError`.

  Cette méthode est appelée dans de rares cas, lorsque le transport (par exemple UDP) détecte qu'un datagramme n'a pas pu être livré à son destinataire. Cependant, il est courant que les datagrammes qui ne peuvent être acheminés soient supprimés silencieusement.

Note

Sur les systèmes BSD (macOS, FreeBSD, etc.), le contrôle de flux n'est pas pris en charge pour les protocoles par datagrammes, car il n'existe aucun moyen fiable de détecter les échecs d'envoi causés par l'écriture d'un trop grand nombre de paquets.

Le connecteur apparaît toujours « prêt » et les paquets en excès sont supprimés. Une `OSError` avec `errno` définie sur `errno.ENOBUFS` peut être levée ou non ; si elle est levée, c'est communiqué à `DatagramProtocol.error_received()` et ignoré dans le cas contraire.

### Protocoles liés aux sous-processus

Les instances de protocole de sous-processus doivent être construites par des fabriques de protocole transmises aux méthodes `loop.subprocess_exec()` et `loop.subprocess_shell()`.

SubprocessProtocol.pipe_data_received(fd, data)

  Appelée lorsqu'un processus enfant écrit sur sa sortie d'erreur ou sa sortie standard.

  fd est l'entier représentant le descripteur de fichier du tube.

  data est un objet bytes non vide contenant les données reçues.

SubprocessProtocol.pipe_connection_lost(fd, exc)

  Appelé lorsqu'un des tubes de communication avec un sous-processus est fermé.

  fd est l'entier représentant le descripteur de fichier qui a été fermé.

SubprocessProtocol.process_exited()

  Appelée lorsqu'un processus enfant se termine.

  It can be called before `pipe_data_received()` and `pipe_connection_lost()` methods.

## Exemples

### Serveur écho en TCP

Crée un serveur d'écho TCP en utilisant la méthode `loop.create_server()`, renvoie les données reçues et ferme la connexion :

    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(
            lambda: EchoServerProtocol(),
            '127.0.0.1', 8888)

        async with server:
            await server.serve_forever()

    asyncio.run(main())

Voir aussi

L'exemple Serveur d'écho TCP utilisant des flux utilise la fonction de haut niveau `asyncio.start_server()`.

### Client écho en TCP

Client d'écho TCP utilisant la méthode `loop.create_connection()` envoie des données et attend que la connexion soit fermée :

    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())

Voir aussi

L'exemple Client d'écho TCP utilisant des flux utilise la fonction de haut niveau `asyncio.open_connection()`.

### Serveur écho en UDP

Serveur d'écho UDP, utilisant la méthode `loop.create_datagram_endpoint()`, qui renvoie les données reçues :

    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(
            lambda: EchoServerProtocol(),
            local_addr=('127.0.0.1', 9999))

        try:
            await asyncio.sleep(3600)  # Serve for 1 hour.
        finally:
            transport.close()

    asyncio.run(main())

### Client écho en UDP

Client d'écho UDP, utilisant la méthode `loop.create_datagram_endpoint()`, qui envoie des données et ferme le transport lorsqu'il reçoit la réponse :

    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())

### Connexion de connecteurs existants

Attend qu'un connecteur reçoive des données en utilisant la méthode `loop.create_connection()` avec un protocole :

    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())

Voir aussi

L'exemple Surveillance des événements de lecture pour un descripteur de fichier utilise la méthode de bas niveau `loop.add_reader()` pour enregistrer un descripteur de fichier.

L'exemple Ouverture d'un connecteur pour attendre les données à l'aide de flux utilise des flux de haut niveau créés par la fonction `open_connection()` dans une coroutine.

### loop.subprocess_exec() et SubprocessProtocol

Un exemple de protocole de sous-processus utilisé pour obtenir la sortie d'un sous-processus et attendre la sortie du sous-processus.

Le sous-processus est créé par la méthode `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}")

Voir aussi le même exemple écrit à l'aide d'API de haut niveau.