2011-08-18 3 views
5

J'ai une infrastructure client/serveur. Actuellement, ils utilisent un TcpClient et un TcpListener pour envoyer une donnée de réception entre tous les clients et le serveur. Ce que je fais actuellement est quand les données sont reçues (sur son propre fil), il est mis dans une file d'attente pour qu'un autre thread traite afin de libérer le socket afin qu'il soit prêt et ouvert pour recevoir de nouvelles données.La meilleure façon d'accepter plusieurs clients tcp?

   // Enter the listening loop. 
       while (true) 
       { 
        Debug.WriteLine("Waiting for a connection... "); 

        // Perform a blocking call to accept requests. 
        using (client = server.AcceptTcpClient()) 
        { 
         data = new List<byte>(); 

         // Get a stream object for reading and writing 
         using (NetworkStream stream = client.GetStream()) 
         { 
          // Loop to receive all the data sent by the client. 
          int length; 

          while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) 
          { 
           var copy = new byte[length]; 
           Array.Copy(bytes, 0, copy, 0, length); 
           data.AddRange(copy); 
          } 
         } 
        } 

        receivedQueue.Add(data); 
       } 

Cependant, je voulais savoir s'il y avait une meilleure façon de le faire. Par exemple s'il y a 10 clients et qu'ils veulent tous envoyer des données au serveur en même temps, l'un passera alors que tous les autres échoueront. Ou si un client a une connexion lente et monopolise le socket toute autre communication s'arrêtera .

N'existe-t-il pas un moyen de recevoir des données de tous les clients en même temps et d'ajouter les données reçues dans la file d'attente pour traitement lorsque le téléchargement est terminé?

+0

Plug sans vergogne: http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you/ - il touche brièvement la boucle async; et contient une implémentation réelle (vous ne devriez pas utiliser 'ThreadPool' comme suggéré par @Jalal). –

Répondre

15

Voici donc une réponse qui vous permettra de démarrer - ce qui est plus de niveau débutant que mon blog post.

.Net a un modèle asynchrone qui tourne autour d'un appel Begin * et End *. Par exemple - BeginReceive et EndReceive. Ils ont presque toujours leur équivalent non-asynchrone (dans ce cas Receive); et atteindre le même objectif. La chose la plus importante à retenir est que les socket font plus que juste faire l'appel asynchrone - ils exposent quelque chose appelé IOCP (IO Completion Ports, Linux/Mono a ces deux mais j'ai oublié le nom) qui est extrêmement important utiliser sur un serveur; Le cœur de ce que fait IOCP est que votre application ne consomme pas de thread pendant qu'elle attend des données.

Comment utiliser le modèle Début/Fin

Chaque méthode Begin * aura exactement deux arguments plus en comparisson à son homologue non-async.Le premier est un AsyncCallback, le second est un objet. Ce que ces deux signifient, c'est "voici une méthode à appeler quand vous avez terminé" et "voici quelques données dont j'ai besoin à l'intérieur de cette méthode". La méthode qui est appelée a toujours la même signature, dans cette méthode vous appelez la contrepartie End * pour obtenir ce qui aurait été le résultat si vous l'aviez fait de manière synchrone. Ainsi, par exemple:

private void BeginReceiveBuffer() 
{ 
    _socket.BeginReceive(buffer, 0, buffer.Length, BufferEndReceive, buffer); 
} 

private void EndReceiveBuffer(IAsyncResult state) 
{ 
    var buffer = (byte[])state.AsyncState; // This is the last parameter. 
    var length = _socket.EndReceive(state); // This is the return value of the method call. 
    DataReceived(buffer, 0, length); // Do something with the data. 
} 

Qu'est-ce qui se passe ici est .Net commence en attente pour les données de la prise, dès qu'il reçoit les données qu'il appelle EndReceiveBuffer et passe à travers les « données personnalisées » (dans ce cas buffer) pour le via state.AsyncResult. Lorsque vous appelez EndReceive, il vous restituera la longueur des données reçues (ou lancera une exception si quelque chose a échoué).

meilleur modèle pour douilles

Ce formulaire vous donnera la gestion des erreurs centrale - il peut être utilisé partout où le motif async enroule un flux comme « chose » (par exemple TCP arrive dans l'ordre qu'il a été envoyé , donc il pourrait être vu comme un objet Stream).

private Socket _socket; 
private ArraySegment<byte> _buffer; 
public void StartReceive() 
{ 
    ReceiveAsyncLoop(null); 
} 

// Note that this method is not guaranteed (in fact 
// unlikely) to remain on a single thread across 
// async invocations. 
private void ReceiveAsyncLoop(IAsyncResult result) 
{ 
    try 
    { 
     // This only gets called once - via StartReceive() 
     if (result != null) 
     { 
      int numberOfBytesRead = _socket.EndReceive(result); 
      if(numberOfBytesRead == 0) 
      { 
       OnDisconnected(null); // 'null' being the exception. The client disconnected normally in this case. 
       return; 
      } 

      var newSegment = new ArraySegment<byte>(_buffer.Array, _buffer.Offset, numberOfBytesRead); 
      // This method needs its own error handling. Don't let it throw exceptions unless you 
      // want to disconnect the client. 
      OnDataReceived(newSegment); 
     } 

     // Because of this method call, it's as though we are creating a 'while' loop. 
     // However this is called an async loop, but you can see it the same way. 
     _socket.BeginReceive(_buffer.Array, _buffer.Offset, _buffer.Count, SocketFlags.None, ReceiveAsyncLoop, null); 
    } 
    catch (Exception ex) 
    { 
     // Socket error handling here. 
    } 
} 

accepte les connexions multiples

ce que vous faites en général est d'écrire une classe qui contient votre prise etc. (ainsi que votre boucle async) et créer un pour chaque client. Ainsi, par exemple:

public class InboundConnection 
{ 
    private Socket _socket; 
    private ArraySegment<byte> _buffer; 

    public InboundConnection(Socket clientSocket) 
    { 
     _socket = clientSocket; 
     _buffer = new ArraySegment<byte>(new byte[4096], 0, 4096); 
     StartReceive(); // Start the read async loop. 
    } 

    private void StartReceive() ... 
    private void ReceiveAsyncLoop() ... 
    private void OnDataReceived() ... 
} 

doit être suivi chaque connexion client par votre classe de serveur (de sorte que vous pouvez les déconnecter proprement lorsque le serveur arrête, ainsi que la recherche/les consulter).

+0

+1 Merci pour tout le matériel génial. – Dylan

+0

J'ai oublié de mentionner que vous pouvez également accepter les clients de la même manière, par ex. BeginAcceptTcpClient. Vous pouvez également configurer la boucle asynchrone de manière identique. –

+1

Le lien du blog est mort. Mais ici, il est sur archive.org: https://web.archive.org/web/20121127003207/http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you –

0

Ce que je fais habituellement utilise un pool de threads avec plusieurs threads. À chaque nouvelle connexion, j'exécute la gestion des connexions (dans votre cas - tout ce que vous faites dans la clause using) dans l'un des threads du pool. Vous atteignez ainsi les deux performances car vous autorisez plusieurs connexions acceptées simultanément et vous limitez également le nombre de ressources (threads, etc.) que vous allouez à la gestion des connexions entrantes.

Vous avez un bel exemple here

Bonne chance

+0

Encore une fois avec la chose Thread-Per-Client. C'est vraiment une mauvaise pratique. –

1

Vous devez utiliser la méthode asynchrone de lecture des données, un exemple est:

// Enter the listening loop. 
while (true) 
{ 
    Debug.WriteLine("Waiting for a connection... "); 

    client = server.AcceptTcpClient(); 

    ThreadPool.QueueUserWorkItem(new WaitCallback(HandleTcp), client); 
} 

private void HandleTcp(object tcpClientObject) 
{ 
    TcpClient client = (TcpClient)tcpClientObject; 
    // Perform a blocking call to accept requests. 

    data = new List<byte>(); 

    // Get a stream object for reading and writing 
    using (NetworkStream stream = client.GetStream()) 
    { 
     // Loop to receive all the data sent by the client. 
     int length; 

     while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) 
     { 
      var copy = new byte[length]; 
      Array.Copy(bytes, 0, copy, 0, length); 
      data.AddRange(copy); 
     } 
    } 

    receivedQueue.Add(data); 
} 

Aussi, vous devriez envisager d'utiliser AutoResetEvent ou ManualResetEvent à être averti lorsque de nouvelles données sont ajoutées à la collection afin que le thread qui gère les données sache quand les données sont reçues, et si vous utilisez 4.0 il vaut mieux désactiver à l'aide BlockingCollection au lieu de Queue.

+0

Vous utilisez déjà un BlockingCollection. Et j'utilise des méthodes synchrones en raison du fait que j'ai un thread dédié pour recevoir les fichiers. Dans votre exemple ci-dessus, si deux clients se connectent en même temps, server.AcceptTcpClient acceptera-t-il les deux ou sera-t-il mis en file d'attente jusqu'à ce que le TcpListener soit disponible (après le HandleTcp)? Notez également que si vous utilisez .Net 4, vous devez utiliser la bibliothèque de tâches au lieu de ThreadPool. – Dylan

+0

Il acceptera les deux, c'est pourquoi nous utilisons ici 'Thread', car il lira les premières données à un autre thread afin qu'il ne bloque pas le thread actuel, donc' ThreadPoo.Queu..' retournera le handle à le fil appelant instantanément et en même temps créer un nouveau fil pour gérer le client. –

+0

-1 pour utiliser ThreadPool. Dans .Net 3.5, vous devriez utiliser 'Begin/End-Receive'. Sur .Net 4.0, vous pouvez utiliser le nouveau modèle d'événement, ou TPL. Votre code n'utilise pas du tout IOCP; ce qui en fait une mauvaise suggestion. –

1

Pour cela, vous devez utiliser la programmation de socket asynchrone. Jetez un oeil à la example fourni par MSDN.

+1

+1 pour citer MSDN - la source de la vérité :). –

Questions connexes