2013-03-07 1 views
3

Désolé pour le post long. Je voudrais utiliser TcpListener pour écouter sur un port, gérer le levage lourd demandé par une connexion entrante dans un autre thread (arrière-plan), puis renvoyer la réponse au client quand il est prêt. J'ai lu beaucoup de code et d'exemples sur MSDN, et viens avec les implémentations suivantes pour le serveur.Quelle est la manière correcte de rendre un TcpListener gérer les connexions de manière asynchrone?

Pour toutes les implémentations ci-dessous, s'il vous plaît prendre les variables suivantes:

let sva = "127.0.0.1" 
let dspt = 32000 

let respondToQuery (ns_ : NetworkStream) (bta_ : byte array) : unit = 
    // DO HEAVY LIFTING 
    () 

MISE EN ŒUVRE 1 (serveur synchrone ordinaire,; ma traduction de the code from this MSDN page)

let runSync() : unit = 
    printfn "Entering runSync()" 
    let (laddr : IPAddress) = IPAddress.Parse sva 
    let (svr : TcpListener) = new TcpListener (laddr, dspt) 
    try 
     svr.Start() 
     let (bta : byte array) = Array.zeroCreate<byte> imbs 
     while true do 
      printfn "Listening on port %d at %s" dspt sva 
      let (cl : TcpClient) = svr.AcceptTcpClient() 
      let (ns : NetworkStream) = cl.GetStream() 
      respondToQuery ns bta 
      cl.Close() 
     svr.Stop() 
     printfn "Exiting runSync() normally" 
    with 
     | excp -> 
      printfn "Error: %s" excp.Message 
      printfn "Exiting runSync() with error" 

MISE EN ŒUVRE 2 (ma traduction du code on this MSDN page)

let runAsyncBE() : unit = 
    printfn "Entering runAsyncBE()" 
    let (tcc : ManualResetEvent) = new ManualResetEvent (false) 
    let (bta : byte array) = Array.zeroCreate<byte> imbs 
    let datcc (ar2_ : IAsyncResult) : unit = 
     let tcpl2 = ar2_.AsyncState :?> TcpListener 
     let tcpc2 = tcpl2.EndAcceptTcpClient ar2_ 
     let (ns2 : NetworkStream) = tcpc2.GetStream() 
     respondToQuery ns2 bta 
     tcpc2.Close() 
     tcc.Set() |> ignore 
    let rec dbatc (tcpl2_ : TcpListener) : unit = 
     tcc.Reset() |> ignore 
     printfn "Listening on port %d at %s" dspt sva 
     tcpl2_.BeginAcceptTcpClient (new AsyncCallback (datcc), tcpl2_) |> ignore 
     tcc.WaitOne() |> ignore 
     dbatc tcpl2_ 
    let (laddr : IPAddress) = IPAddress.Parse sva 
    let (tcpl : TcpListener) = new TcpListener (laddr, dspt) 
    try 
     tcpl.Start() 
     dbatc tcpl 
     printfn "Exiting try block" 
     printfn "Exiting runAsyncBE() normally" 
    with 
     | excp -> 
      printfn "Error: %s" excp.Message 
      printfn "Exiting runAsyncBE() with error" 

MISE EN ŒUVRE 3 (ma mise en œuvre basée sur the MSDN page for asynchronous workflows)

let runAsyncA() : unit = 
    printfn "Entering runAsyncA()" 
    let (laddr : IPAddress) = IPAddress.Parse sva 
    let (svr : TcpListener) = new TcpListener (laddr, dspt) 
    try 
     svr.Start() 
     let (bta : byte array) = Array.zeroCreate<byte> imbs 
     while true do 
      printfn "Listening on port %d at %s" dspt sva 
      let (cl : TcpClient) = svr.AcceptTcpClient() 
      let (ns : NetworkStream) = cl.GetStream() 
      async {respondToQuery ns bta} |> Async.RunSynchronously 
      cl.Close() 
     svr.Stop() 
     printfn "Exiting runAsyncA() normally" 
    with 
     | excp -> 
      printfn "Error: %s" excp.Message 
      printfn "Exiting runAsyncA() with error" 

Maintenant, de ma lecture de la documentation MSDN, je l'aurais pensé que Implementation 3 aurait été le plus rapide. Mais quand je frappe le serveur avec plusieurs requêtes de plusieurs machines, elles fonctionnent toutes à peu près à la même vitesse. Cela m'amène à croire que je dois faire quelque chose de mal.

est soit Implementation 2 ou Implementation 3 la « bonne » façon de mettre en œuvre un TcpListener qui fait son levage de charges lourdes en arrière-plan, et renvoie la réponse au client quand il est terminé, tout en permettant un autre client peut également se connecter et commencer une autre tâche dans un autre thread d'arrière-plan? Si non, pourriez-vous me dire quelles classes (ou tutoriels) devrais-je lire?

Répondre

6

La bonne structure pour la boucle principale devrait ressembler à ceci:

let respondToQuery (client:TcpClient) = async { 
    try 
    let stream = client.GetStream() 
    () // TODO: The actual processing goes here! 
    finally 
    client.Close() } 

async { 
    while true do 
    let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask 
    respondToQuery client |> Async.Start } 

Les choses importantes à noter sont:

  • J'Enveloppez la boucle principale à l'intérieur async de sorte que vous pouvez attendre clients utilisant de manière asynchrone AcceptTcpClientAsync (sans y bloquer)

  • La fonction respondToQuery retourne un n calcul asynchrone qui est démarré en arrière-plan en utilisant Async.Start, de sorte que le traitement peut se poursuivre en parallèle avec l'attente du prochain client (lors de l'utilisation Async.RunSynchronously vous bloquer et attendre que respondToQuery finalise)

  • Pour que cela soit totalement asynchrone, la code à l'intérieur respondToQuery doit également utiliser les opérations asynchrones du flux - Rechercher AsyncRead et AsyncWrite.

Vous pouvez également utiliser Async.StartChild, auquel cas le calcul de l'enfant (corps de respondToQuery) obtient le même ordre d'idées d'annulation en tant que parent et donc quand vous annulez le principal flux de travail asynchrone, il annulera tous les enfants aussi:

while true do 
    let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask 
    do! respondToQuery client |> Async.StartChild |> Async.Ignore } 

la méthode Async.StartChild retourne un calcul asynchrone (à commencer à utiliser let! ou do!) et nous devons ignorer le jeton qu'il retourne (peut être utilisé pour attendre jusqu'à ce que l'enfant complète).

+0

Merci beaucoup! – Shredderroy

+0

Pouvez-vous utiliser 'use client = client' au lieu de' try..finally'? –

Questions connexes