2016-03-18 1 views
4

Actuellement, je travaille sur un simple programme de chat de serveur client (voulait entrer dans les choses de communication du serveur client en C#). Cela fonctionne jusqu'à maintenant, sauf pour se déconnecter correctement du serveur.Problèmes avec la fermeture de TcpClient et NetworkStream

Sur le client j'utilise ce code ici pour fermer la connexion:

client.Client.Disconnect(false); // client is the TcpClient 
client.Close(); 

Sur le serveur il y a une boucle filetée en attente pour les messages du client:

private void StartChat() 
{ 
    int requestCount = 0; 
    byte[] bytesFrom = new byte[10025]; 
    string dataFromClient = null; 
    string rCount = null; 

    while (true) 
    { 
     try 
     { 
      requestCount++; 

      NetworkStream stream = tcpClient.GetStream(); 

      int bufferSize = (int)tcpClient.ReceiveBufferSize; 
      if (bufferSize > bytesFrom.Length) 
      { 
       bufferSize = bytesFrom.Length; 
      } 

      stream.Read(bytesFrom, 0, bufferSize); 
      dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom); 
      dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$")); 
      rCount = Convert.ToString(requestCount); 

      string message = client.Name + " says: " + dataFromClient; 
      program.Broadcast(message); 

     } 
     catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException) 
     { 
      program.UserDisconnected(client); 
      break; 
     } 
     catch(ArgumentOutOfRangeException ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
      break; 
     } 
     catch(Exception ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
      break; 
     } 
    } 

Si le client se déconnecte avec le code indiqué ci-dessus, la fonction récupère le flux tout le temps et produit une telle sortie:

\0\0\0\0\0\0\0 [and so on]

Dans ce cas, le ArgumentOutOfRangeException sera lancé car il n'y a pas d'index de $. J'ai ajouté un break pour éviter et l'exécution sans fin de la boucle.

Étonnamment, le ObjectDisposedException ne sera pas lancé. De plus, le System.IO.IOException ne sera pas lancé mais il devrait l'être parce que le flux a été fermé, donc la connexion a été refusée.

Si je ferme simplement l'application client qui est connectée au serveur, le serveur n'arrête pas la boucle, il attend juste un flux qui ne viendra jamais parce que le client s'est déconnecté. Comment puis-je détecter si le client est déconnecté ou n'est plus joignable?

Et la façon dont je ferme la connexion client est-elle un bon moyen de fermer une connexion?

Merci pour votre aide!

Mise à jour:

private void StartChat() 
{ 
    int requestCount = 0; 
    byte[] bytesFrom = new byte[10025]; 
    string dataFromClient = null; 
    string rCount = null; 

    while (true) 
    { 
     try 
     { 
      requestCount++; 

      NetworkStream stream = tcpClient.GetStream(); 
      stream.ReadTimeout = 4000; 

      int bufferSize = (int)tcpClient.ReceiveBufferSize; 
      if (bufferSize > bytesFrom.Length) 
      { 
       bufferSize = bytesFrom.Length; 
      } 


      // Wait for a client message. If no message is recieved within the ReadTimeout a IOException will be thrown 
      try 
      { 
       int bytesRead = stream.Read(bytesFrom, 0, bufferSize); 
       stream.Flush(); 

       if (bytesRead == 0) 
       { 
        throw new System.IO.IOException("Connection seems to be refused or closed."); 
       } 
      } 
      catch (System.IO.IOException) 
      { 
       byte[] ping = System.Text.Encoding.UTF8.GetBytes("%"); 
       stream.WriteTimeout = 1; 

       stream.Write(ping, 0, ping.Length); 
       continue; 
      } 


      dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom); 
      dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$")); 
      rCount = Convert.ToString(requestCount); 

      string message = client.Name + " says: " + dataFromClient; 
      program.Broadcast(message); 

     } 
     catch(Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is System.IO.IOException) 
     { 
      Debug.WriteLine(ex.ToString()); 
      program.UserDisconnected(client); 
      break; 
     } 
     catch(ArgumentOutOfRangeException ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
     } 
     catch(Exception ex) 
     { 
      Debug.WriteLine(ex.ToString()); 
      break; 
     } 
    } 
} 

Répondre

3

Vous devriez vérifier la valeur de retour de stream.Read - elle renvoie le nombre d'octets effectivement lecture.

Ce sera 0 lorsque le client s'est déconnecté, et lorsqu'il lit des données, le nombre d'octets lus est souvent inférieur à la taille de la mémoire tampon.

int bytes = stream.Read(bytesFrom, 0, bufferSize); 
if (bytes == 0) 
{ 
    // client has disconnected 
    break; 
} 

dataFromClient = System.Text.Encoding.UTF8.GetString(bytesFrom, 0, bytes); 

En réponse à votre commentaire récent, trois choses peuvent se produire lorsqu'un client met fin à sa connexion:

  • Le client ferme correctement la connexion, et que vous recevez 0 octets
  • Quelque chose détectable est arrivé, provoquant une exception
  • Le client est « disparu », mais aucun signal est envoyé à votre fin

Dans cette dernière situation, le serveur agira toujours comme s'il y avait une connexion active, jusqu'à ce qu'il essaie d'envoyer des données, ce qui échouerait évidemment. C'est pourquoi de nombreux protocoles fonctionnent avec un timeout de connexion et/ou un mécanisme keep-alive.

+0

Est-ce que cela signifie que vous ne pouvez pas détecter si le client est parti? En regardant le docu, il devrait lancer une exception ObjectDispoedException ou au moins une exception IOException car il ne peut plus en lire – chris579

+0

Une déconnexion _graceful_ n'est pas un cas exceptionnel, et est détectée par 'stream.Read()' renvoyant 0. Vous devriez en effet avoir un gestionnaire d'exception, mais je doute que 'ObjectDisposedException' soit levé à moins que vous ne le disposiez vous-même, puis essayez de l'utiliser. –

+0

Vous avez raison, DisposedException ne sera pas levée, sauf si cet objet est toujours là et n'est pas supprimé par garbage collector ou manuellement éliminé. Cependant, merci beaucoup, cela a fonctionné! Je vais accepter votre réponse dès que possible. Éditer: Si je ferme le client, la boucle ne se terminera pas et restera jusqu'à ce que l'application serveur soit fermée. Comment pourrais-je résoudre ce problème? – chris579

1

int partesRead = flux.Read (...) if (bytesRead == 0) // client déconnecté

+0

Eh bien, vous devez ralentir. Regardez la réponse ci-dessus;) – chris579

+0

ya ... Je sais qu'il y a assez de développeurs ici, peut-être que le site pourrait laisser savoir à quelqu'un quand il y a un nouveau commentaire/réponse ... et réparer la pagination pendant qu'ils y sont : p – ABuckau

+0

Ouais :) Il y a encore un problème en suspens il suffit de regarder mon commentaire dans la première réponse. Peut-être que vous pouvez m'aider avec cela;) – chris579