2011-04-27 1 views
1

Je construis un client IRC et je veux une classe de réseau IRC indépendante qui envoie et reçoit des lignes entre le client et le serveur. L'envoi de lignes est OK, mais je ne sais pas comment mettre en place un thread d'écoute. Toutes les méthodes que j'ai essayé fonctionnent, mais j'ai des défauts que je ne peux pas contourner.Quelle est la bonne façon d'implémenter une classe de ligne réseau

Ma première méthode consistait à créer un StreamReader appelé _reader en utilisant TcpClient.GetStream() comme base, et faire juste un _reader.ReadLine(). Cela présente l'avantage que le thread d'écoute d'arrière-plan s'arrête automatiquement jusqu'à ce qu'une ligne soit reçue. Le problème est que lorsque je déconnecte et arrête le thread d'écoute (ou l'application quitte simplement), je ne suis pas sûr à quel point il est bon pour la collecte de place etc que le thread avec l'attente ReadLine() vient d'être tué. Le thread pourrait devenir orphelin et rester en quelque sorte en arrière-plan en train de voler des ressources?

Ma deuxième méthode était de créer un NetworkStream appelé _reader basé sur le même TcpClient.GetStream() et créer une boucle qui vérifie _reader.DataAvailable et continue la boucle while si elle est fausse, et par ailleurs pousser les octets dans un StringBuilder qui est instantanément vérifiée \r\n et extrait toutes les lignes entières. Cela donne le même effet que la ReadLine sur la récupération de données, mais je n'aime pas la boucle constante _reader.DataAvailable. Sur mon processeur Core i5, il ne prend pas de CPU mais sur mon ordinateur portable i9 beaucoup plus puissant il vole un CPU virtuel en permanence. "Résout" cela, mais semble être une solution sale, et de nombreux articles sur Internet classent cela comme une odeur de code.

Alors, quelle serait la bonne solution?

Répondre

1

Si vous annulez le thread d'écoute, alors c'est mauvais. Mais si l'application se termine (ou si vous fermez le socket sous-jacent), tout ira bien.

Listez la plupart des autres, je ne recommande pas d'interroger. Je suggère une solution qui utilise des lectures et des écritures asynchrones, bien qu'il soit beaucoup plus difficile à programmer.

+0

J'ai regardé les méthodes asynchrones, mais la documentation de MSDN - et particulièrement l'exemple de code - est pleine de failles et d'erreurs. Je n'ai toujours pas trouvé un bon guide sur la façon de les utiliser. – carlsb3rg

+0

True. Les ressources les plus utiles concernent les applications TCP/IP non gérées.J'ai quelques lignes directrices dans la section 2 de ma [FAQ] (http://nitoprograms.blogspot.com/2009/04/tcpip-net-sockets-faq.html), mais ce n'est pas du matériel de niveau tutoriel. –

+0

Je n'annule jamais le fil. C'est un thread d'arrière-plan qui meurt automagiquement lorsque l'application se ferme. La boucle d'écoute est 'while (tcpClient.Connected) {...}'. Si j'utilise '.ReadLine()' alors il n'y a aucun moyen de forcer l'exécution du programme à continuer à la prochaine 'while' vérifier et terminer la boucle gracieusement. C'est pourquoi je veux utiliser la méthode NetworkStream car je peux boucler en continu tant que les données ne sont pas disponibles et quitter gracieusement si nécessaire. J'ai aimé votre FAQ thugh. Vous devriez contourner les bits qui sont pour une entrée futre FAQ. – carlsb3rg

0

Voici quelque chose pour vous lancer.

Notez qu'il n'est pas très efficace d'utiliser string lors de la génération des messages. Mais cela rend tout plus lisible.

public abstract class LineBasedChannel 
{ 
    Socket _socket; 
    string _inbuffer = string.Empty; 
    Encoding _encoding; 
    byte[] _buffer = new byte[8192]; 

    public LineBasedChannel(Socket socket) 
    { 
     _socket = socket; 
     _encoding = Encoding.ASCII; 
     _sb = new StringBuilder(); 
    } 

    public void Start() 
    { 
     _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null); 
    } 

    public void OnRead(IAsyncResult res) 
    { 
     var bytesRead = _socket.EndReceive(res); 
     if (bytesRead == 0) 
     { 
      HandleDisconnect(); 
      return; 
     } 

     _inbuffer += _encoding.GetString(_buffer, 0, bytesRead); 
     _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null); 

     while (true) 
     { 
      int pos = _inbuffer.IndexOf("\r\n"); 
      if (pos == -1) 
       break; 

      OnReceivedLine(_inbuffer.SubString(0, pos+2); 
      _inbuffer = _inbuffer.Remove(0,pos+1); 
     } 
    } 

    protected abstract void OnReceivedLine(string line); 
} 

public class IrcTcpChannel : LineBasedChannel 
{ 
    protected override void OnReceivedLine(string line) 
    { 
     var cmd = new IrcCommand(); 
     cmd.Channel = line.SubString(x,y); 
     cmd.Message = line.SubString(z,a); 
     CommandReceived(this, new IrcCommandEventArgs(cmd)); 
    } 

    public event EventHandler<IrcCommandEventArgs> CommandReceived = delegate {}; 
} 

Notez également que je ne géraient pas des exceptions dans ce code, qui est obligatoire. Toute exception non gérée dans les méthodes asynchrones mettra fin à votre application.

0

Old question, le problème doit avoir réglé il y a longtemps. Quoi qu'il en soit, il ressemble beaucoup à la question encore plus ancienne c# - What is a good method to handle line based network I/O streams? - Stack Overflow

Si oui, la réponse devrait fournir une partie de la solution. Fondamentalement, c'est une classe qui fournit une méthode Process(byte[]) retournant un IEnumerable<string>, qui maintient l'état du contenu partiel ne formant pas encore une ligne complète. L'utilisation de cela permettra de séparer les préoccupations. Il suffit de lire les données telles qu'elles sont disponibles. Chaque fois qu'un morceau est lu, alimentez-le dans cette méthode, vous obtenez des lignes comme ils sont complètement formés.

Questions connexes