🚧 Attention, peinture fraüche !

Cette page a Ă©tĂ© traduite par une seule personne et n'a pas Ă©tĂ© relue et vĂ©rifiĂ©e par quelqu'un d'autre ! Les informations peuvent par exemple ĂȘtre erronĂ©es, ĂȘtre formulĂ©es maladroitement, ou contenir d'autres types de fautes.

GĂ©rer les connexions en concurrence

Le problÚme avec notre code précédent est que ecouteur.incoming() est un itérateur bloquant. L'exécuteur ne peut pas exécuter d'autres futures pendant que ecouteur attends les connexions entrantes, et nous ne pouvons pas gérer une nouvelle connexion jusqu'à ce que nous ayons terminé avec la précédente.

Pour corriger cela, nous allons transformer l'itĂ©rateur bloquant ecouteur.incoming() en Stream non bloquant. Les Streams ressemblent aux itĂ©rateurs, mais peuvent ĂȘtre consommĂ©s de maniĂšre asynchrone. Pour plus d'informations, vous pouvez consulter le chapitre sur les Streams.

Remplaçons notre std::net::TcpListener bloquant par le async_std::net::TcpListener non bloquant, et mettons à jour notre gestion de connexion pour accepter un async_std::net::TcpStream :

use async_std::prelude::*;

async fn gestion_connexion(mut flux: TcpStream) {
    let mut tampon = [0; 1024];
    flux.read(&mut tampon).await.unwrap();

    //<-- partie masquée ici -->
    flux.write(reponse.as_bytes()).await.unwrap();
    flux.flush().await.unwrap();
}

La version asynchrone de TcpListener implémente le trait Stream sur ecouteur.incoming(), ce qui apporte deux avantages. Le premier est que ecouteur.incoming() ne bloque plus l'exécuteur. L'exécuteur peut maintenant transférer l'exécution à d'autres futures en attente lorsqu'il n'y a plus de connexions TCP entrantes à traiter.

Le second bienfait est que les Ă©lĂ©ments du Stream peuvent optionnellement ĂȘtre traitĂ©s en concurrence, en utilisant la mĂ©thode for_each_concurrent des Streams. Ici, nous allons profiter de cette mĂ©thode pour traiter chaque requĂȘte entrante de maniĂšre concurrente. Nous avons besoin d'importer le trait Stream de la crate futures, donc notre Cargo.toml ressemble maintenant Ă  ceci :

+[dependencies]
+futures = "0.3"

 [dependencies.async-std]
 version = "1.6"
 features = ["attributes"]

Maintenant, nous pouvons traiter chaque connexion en concurrence en passant gestion_connexion dans une fermeture. La fermeture prend possession de chaque TcpStream, et est exĂ©cutĂ© dĂšs qu'un nouveau TcpStream est disponible. Tant que gestion_connexion ne bloque pas, une rĂ©ponse lente ne va plus empĂȘcher les autres requĂȘtes de se complĂ©ter.

use async_std::net::TcpListener;
use async_std::net::TcpStream;
use futures::stream::StreamExt;

#[async_std::main]
async fn main() {
    let ecouteur = TcpListener::bind("127.0.0.1:7878").await.unwrap();
    ecouteur
        .incoming()
        .for_each_concurrent(/* limite */ None, |flux_tcp| async move {
            let flux_tcp = flux_tcp.unwrap();
            gestion_connexion(flux_tcp).await;
        })
        .await;
}

Servir les requĂȘtes en parallĂšle

Notre exemple jusqu'Ă  prĂ©sent a largement prĂ©sentĂ© la concurrence (en utilisant du code asynchrone) comme Ă©tant une alternative au parallĂ©lisme (en utilisant des processus). Cependant, le code asynchrone et les processus ne s'excluent pas mutuellement. Dans notre exemple, for_each_concurrent traite chaque connexion en concurrence, mais sur le mĂȘme processus. La crate async-std nous permet Ă©galement de crĂ©er des tĂąches sur des processus sĂ©parĂ©s. Comme gestion_connexion est Ă  la fois Send et non bloquant, il est sĂ»r Ă  utiliser avec async_std::task::spawn. Voici Ă  quoi cela devrait ressembler :

use async_std::task::spawn;

#[async_std::main]
async fn main() {
    let ecouteur = TcpListener::bind("127.0.0.1:7878").await.unwrap();
    ecouteur
        .incoming()
        .for_each_concurrent(/* limite */ None, |flux| async move {
            let flux = flux.unwrap();
            spawn(gestion_connexion(flux));
        })
        .await;
}

Maintenant nous utilisons Ă  la fois la concurrence et le parallĂ©lisme pour traiter plusieurs requĂȘtes en mĂȘme temps ! Lisez la section sur les exĂ©cuteurs multi-processus pour en savoir plus.