J'utilise la bibliothèque websockets
pour créer un serveur websocket dans Python 3.4. Voici un serveur d'écho simple:
import asyncio
import websockets
@asyncio.coroutine
def connection_handler(websocket, path):
while True:
msg = yield from websocket.recv()
if msg is None: # connection lost
break
yield from websocket.send(msg)
start_server = websockets.serve(connection_handler, 'localhost', 8000)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
Disons que nous - en plus - voulions envoyer un message au client chaque fois qu'un événement se produit. Par souci de simplicité, envoyons un message périodiquement toutes les 60 secondes. Comment ferions-nous cela? Je veux dire, parce que connection_handler
attend constamment les messages entrants, le serveur ne peut agir qu'après avoir reçu un message du client, non? Qu'est-ce que j'oublie ici?
Peut-être que ce scénario nécessite un cadre basé sur des événements/rappels plutôt qu'un cadre basé sur des coroutines? Tornade ?
TL; DR Utilisez asyncio.ensure_future()
pour exécuter plusieurs coroutines simultanément.
Peut-être que ce scénario nécessite un cadre basé sur des événements/rappels plutôt qu'un cadre basé sur des coroutines? Tornade?
Non, vous n'avez besoin d'aucun autre framework pour cela. L'idée générale de l'application asynchrone vs synchrone est qu'elle ne se bloque pas, en attendant le résultat. Peu importe comment il est implémenté, en utilisant des coroutines ou des rappels.
Je veux dire, parce que connection_handler attend constamment les messages entrants, le serveur ne peut agir qu'après avoir reçu un message du client, non? Qu'est-ce que j'oublie ici?
Dans une application synchrone, vous écrirez quelque chose comme msg = websocket.recv()
, qui bloquerait toute l'application jusqu'à ce que vous receviez un message (comme vous l'avez décrit). Mais dans l'application asynchrone, c'est complètement différent.
Quand vous faites msg = yield from websocket.recv()
vous dites quelque chose comme: suspendre l'exécution de connection_handler()
jusqu'à ce que websocket.recv()
produise quelque chose. L'utilisation de yield from
À l'intérieur de la coroutine renvoie le contrôle à la boucle d'événements, de sorte qu'un autre code peut être exécuté pendant que nous attendons le résultat de websocket.recv()
. Veuillez vous référer à documentation pour mieux comprendre le fonctionnement des coroutines.
Disons que nous - en plus - voulions envoyer un message au client chaque fois qu'un événement se produit. Par souci de simplicité, envoyons un message périodiquement toutes les 60 secondes. Comment ferions-nous cela?
Vous pouvez utiliser asyncio.async()
pour exécuter autant de coroutines que vous le souhaitez, avant d'exécuter l'appel de blocage pour démarrage de la boucle d'événements .
import asyncio
import websockets
# here we'll store all active connections to use for sending periodic messages
connections = []
@asyncio.coroutine
def connection_handler(connection, path):
connections.append(connection) # add connection to pool
while True:
msg = yield from connection.recv()
if msg is None: # connection lost
connections.remove(connection) # remove connection from pool, when client disconnects
break
else:
print('< {}'.format(msg))
yield from connection.send(msg)
print('> {}'.format(msg))
@asyncio.coroutine
def send_periodically():
while True:
yield from asyncio.sleep(5) # switch to other code and continue execution in 5 seconds
for connection in connections:
print('> Periodic event happened.')
yield from connection.send('Periodic event happened.') # send message to each connected client
start_server = websockets.serve(connection_handler, 'localhost', 8000)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.async(send_periodically()) # before blocking call we schedule our coroutine for sending periodic messages
asyncio.get_event_loop().run_forever()
Voici un exemple d'implémentation client. Il vous demande d'entrer un nom, le reçoit du serveur d'écho, attend deux autres messages du serveur (qui sont nos messages périodiques) et ferme la connexion.
import asyncio
import websockets
@asyncio.coroutine
def hello():
connection = yield from websockets.connect('ws://localhost:8000/')
name = input("What's your name? ")
yield from connection.send(name)
print("> {}".format(name))
for _ in range(3):
msg = yield from connection.recv()
print("< {}".format(msg))
yield from connection.close()
asyncio.get_event_loop().run_until_complete(hello())
Les points importants:
asyncio.async()
a été renommé asyncio.ensure_future()
.Même problème, je peux difficilement trouver de solution avant d'avoir vu l'exemple parfait ici: http://websockets.readthedocs.io/en/stable/intro.html#both
done, pending = await asyncio.wait(
[listener_task, producer_task],
return_when=asyncio.FIRST_COMPLETED) # Important
Ainsi, je peux gérer des tâches multi-coroutines telles que le battement de coeur et le redis subscribe.
Je suis surpris gather
n'est pas mentionné.
De la documentation Python :
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({i})...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")
async def main():
# Schedule three calls *concurrently*:
await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
)
asyncio.run(main())
# Expected output:
#
# Task A: Compute factorial(2)...
# Task B: Compute factorial(2)...
# Task C: Compute factorial(2)...
# Task A: factorial(2) = 2
# Task B: Compute factorial(3)...
# Task C: Compute factorial(3)...
# Task B: factorial(3) = 6
# Task C: Compute factorial(4)...
# Task C: factorial(4) = 24