2013-04-10 5 views
0

J'essaie de concevoir un service client de messagerie qui permet de se connecter et d'envoyer/de recevoir des commandes en utilisant la classe TcpClient. Je veux également invoquer automatiquement le thread appelant lors du rappel de chaque fonction afin que l'appelant n'en ait pas besoin.Modèle de conception de service client asynchrone

Je trouve que j'ai besoin d'écrire au moins trois à quatre fois plus de code par fonction pour l'implémenter que si je devais l'écrire de manière synchrone. Le plus gros problème est que je dois écrire un try/catch séparé pour chaque callback.

Je posterai ma fonction Connect et nous espérons que quelqu'un peut suggérer une meilleure façon:

public virtual void Connect(Action<Exception> callback, string hostname, int port, bool ssl, RemoteCertificateValidationCallback validateCertificate) 
{ 
    if (State != ConnectionState.Disconnected) 
     throw new InvalidOperationException(AlreadyConnectedString); 

    Host = hostname; 
    Port = port; 
    Ssl = ssl; 

    var context = SynchronizationContext.Current; 

    // Callback on the caller's thread 
    Action<Exception> onCallback = (Exception ex) => 
     { 
      context.Post(_ => 
       { 
        callback(ex); 
       }, null); 
     }; 

    // Called on any raised exceptions 
    Action<Exception> onFail = (Exception ex) => 
     { 
      State = ConnectionState.Disconnected; 
      Cleanup(); 
      onCallback(ex); 
     }; 

    // Check for a valid response 
    Action<string, Exception> onConnectResponse = (string response, Exception ex) => 
     { 
      if (ex != null) 
       onFail(ex); 

      try 
      { 
       OnConnected(response); 
       onCallback(ex); 
      } 
      catch (Exception responseException) 
      { 
       onFail(responseException); 
      } 
     }; 

    // Callback after SSL authentication 
    AsyncCallback onAuthenticated = (IAsyncResult result) => 
     { 
      try 
      { 
       var sslStream = (SslStream)result.AsyncState; 
       sslStream.EndAuthenticateAsClient(result); 

       State = ConnectionState.Authorization; 

       GetResponse(onConnectResponse); 
      } 
      catch (Exception authenticateException) 
      { 
       onFail(authenticateException); 
      } 
     }; 

    // Callback after TcpClient connect 
    AsyncCallback onConnect = (IAsyncResult result) => 
     { 
      try 
      { 
       _Connection.EndConnect(result); 

       _Stream = _Connection.GetStream(); 

       if (ssl) 
       { 
        SslStream sslStream; 

        if (validateCertificate != null) 
         sslStream = new SslStream(_Stream, false, validateCertificate); 
        else 
         sslStream = new SslStream(_Stream, false); 

        _Stream = sslStream; 

        sslStream.BeginAuthenticateAsClient(hostname, onAuthenticated, sslStream); 
       } 
       else 
       { 
        State = ConnectionState.Authorization; 

        GetResponse(onConnectResponse); 
       } 
      } 
      catch (Exception connectException) 
      { 
       onFail(connectException); 
      } 
     }; 

    try 
    { 
     _Connection = new TcpClient(); 
     _Connection.BeginConnect(hostname, port, onConnect, null); 
    } 
    catch (Exception ex) 
    { 
     onFail(ex); 
    } 
} 
+0

Avez-vous envisagé d'utiliser une bibliothèque réseau pour faire cela pour vous? Commander http://www.networkcomms.net/how-to-create-a-client-server-application-in-minutes/. Disclaimer - Je suis un développeur pour cette bibliothèque. – MarcF

Répondre

0

Voici le format général de code que je l'ai utilisé avec succès jusqu'à présent.

Il ne bloque pas le thread appelant (UI) et il invoque toujours ses rappels sur le thread appelant, mais il effectue la majeure partie du travail de chaque tâche dans les threads d'arrière-plan. Si un thread a besoin d'un accès exclusif à une ressource partagée (comme un flux réseau/SSL ouvert), vous pouvez utiliser un locker pour gérer l'utilisation de cette ressource afin qu'un seul thread l'utilise à la fois.

public void Connect(Action<Exception> callback, string hostname, int port, bool ssl, RemoteCertificateValidationCallback validateCertificate) 
{ 
    if (State != ConnectionState.Disconnected) 
     throw new InvalidOperationException(AlreadyConnectedString); 

    Host = hostname; 
    Port = port; 
    Ssl = ssl; 
    State = ConnectionState.Connecting; 

    var callingThread = TaskScheduler.FromCurrentSynchronizationContext(); 

    Action connectAction =() => 
    { 
     // Connect asynchronously in order to specify a timeout 
     TcpClient connection = new TcpClient(); 
     connection.SendTimeout = SendTimeout; 
     connection.ReceiveTimeout = ReadTimeout; 

     IAsyncResult ar = connection.BeginConnect(hostname, port, null, null); 
     WaitHandle waitHandle = ar.AsyncWaitHandle; 

     try 
     { 
      if (!ar.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout), false)) 
       throw new TimeoutException(); 

      connection.EndConnect(ar); 
     } 
     finally 
     { 
      waitHandle.Close(); 
     } 

     Stream stream = connection.GetStream(); 

     if (ssl) 
     { 
      SslStream sslStream; 

      if (validateCertificate != null) 
       sslStream = new SslStream(stream, false, validateCertificate); 
      else 
       sslStream = new SslStream(stream, false); 

      sslStream.AuthenticateAsClient(hostname); 

      stream = sslStream; 
     } 

     lock (_locker) // Perform thread unsafe operations here 
     { 
      _connection = connection; 
      _stream = stream; 
     } 

     OnConnected(GetResponse()); 
    }; 

    Action<Task> completeAction = (Task task) => 
    { 
     Exception ex = (task.Exception != null) ? task.Exception.InnerException : task.Exception; 

     if (task.Exception != null) 
     { 
      Cleanup(); 
     } 
     else 
     { 
      State = ConnectionState.Authorization; 
     } 

     if (callback != null) 
      callback(ex); 
    }; 

    Task.Factory.StartNew(connectAction, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) 
       .ContinueWith(completeAction, callingThread); 
} 
Questions connexes