Un de mes amis est venu me voir avec un problème: lorsque vous utilisez la classe NetworkStream à l'extrémité du serveur de la connexion, si le client se déconnecte, NetworkStream ne parvient pas à le détecter.Détection déconnexion TCP client en utilisant la classe NetworkStream
Minimaliste, son code C# ressemblait à ceci:
List<TcpClient> connections = new List<TcpClient>();
TcpListener listener = new TcpListener(7777);
listener.Start();
while(true)
{
if (listener.Pending())
{
connections.Add(listener.AcceptTcpClient());
}
TcpClient deadClient = null;
foreach (TcpClient client in connections)
{
if (!client.Connected)
{
deadClient = client;
break;
}
NetworkStream ns = client.GetStream();
if (ns.DataAvailable)
{
BinaryFormatter bf = new BinaryFormatter();
object o = bf.Deserialize(ns);
ReceiveMyObject(o);
}
}
if (deadClient != null)
{
deadClient.Close();
connections.Remove(deadClient);
}
Thread.Sleep(0);
}
Le code fonctionne, en ce que les clients peuvent se connecter avec succès et le serveur peut lire les données qui lui sont envoyées. Toutefois, si le client distant appelle tcpClient.Close(), le serveur ne détecte pas la déconnexion - client.Connected reste vrai et ns.DataAvailable est faux.
Une recherche de Stack Overflow a fourni et réponse - puisque Socket.Receive n'est pas appelé, le socket ne détecte pas la déconnexion. C'est suffisant. Nous pouvons travailler autour que:
foreach (TcpClient client in connections)
{
client.ReceiveTimeout = 0;
if (client.Client.Poll(0, SelectMode.SelectRead))
{
int bytesPeeked = 0;
byte[] buffer = new byte[1];
bytesPeeked = client.Client.Receive(buffer, SocketFlags.Peek);
if (bytesPeeked == 0)
{
deadClient = client;
break;
}
else
{
NetworkStream ns = client.GetStream();
if (ns.DataAvailable)
{
BinaryFormatter bf = new BinaryFormatter();
object o = bf.Deserialize(ns);
ReceiveMyObject(o);
}
}
}
}
(j'ai laissé de côté le code de gestion des exceptions pour la brièveté.)
Ce code fonctionne, cependant, je ne dirais pas cette solution « élégante ». L'autre solution élégante au problème que je connais est de générer un thread par TcpClient, et permettre à l'appel BinaryFormatter.Deserialize (née NetworkStream.Read) de bloquer, ce qui permettrait de détecter la déconnexion correctement. Cependant, cela entraîne la création et la maintenance d'un thread par client.
Je sens que je manque une réponse secrète, impressionnante qui conserverait la clarté du code d'origine, mais il faut éviter l'utilisation de threads supplémentaires pour effectuer les lectures asynchrones. Bien que, peut-être, la classe NetworkStream n'a jamais été conçue pour ce genre d'utilisation. Quelqu'un peut-il nous éclairer?
Mise à jour: voudrais préciser que je suis curieux de voir si le framework .NET a une solution qui couvre cette utilisation de NetworkStream (à savoir l'interrogation et évitant le blocage) - évidemment il peut être fait; le NetworkStream pourrait facilement être enveloppé dans une classe de support qui fournit la fonctionnalité. Il semblait juste étrange que le framework nécessite essentiellement d'utiliser des threads pour éviter de bloquer sur NetworkStream.Read, ou de jeter un coup d'œil sur le socket lui-même pour vérifier les déconnexions - presque comme si c'était un bug. Ou un manque potentiel d'une fonctionnalité. ;)
Cela permet la détection de déconnexion en interprétant le résultat de la lecture des appels. Cependant, ma curiosité provient de l'absence apparente du cadre d'une «autre» façon de le faire, surtout compte tenu de l'élégance qu'elle confère à l'implémentation originale. En outre, le code d'origine délimitait correctement les objets, de sorte que plusieurs messages pouvaient être envoyés. Je suppose que cela est dû à la façon dont BinaryFormatter sérialise les objets - il sait par nature quand il est "fait". (Cependant, le code que vous avez présenté inclut le besoin d'un délimiteur.) –
Pouvez-vous me dire comment voudriez-vous que cela soit implémenté dans le framework? Le Framework est juste un wrapper au-dessus des sockets, ce qui est un mécanisme de transport. D'autres sémantiques doivent être implémentées par l'application. En outre, BinaryFormatter prend un tampon en entrée pour la désérialisation, vous devez donc lui donner le tampon exact nécessaire pour récupérer l'objet. Sinon, il ne fonctionnera pas comme prévu. – feroze
Vous avez raison - la désérialisation nécessite un tampon exact ou elle va mourir douloureusement, et ce n'est pas la responsabilité de BinaryFormatter de s'assurer que le flux est complet; dans ce cas, ce n'est pas non plus NetworkStream. Je pense que j'y ai peut-être mal pensé et que ce n'est pas la responsabilité du Cadre, mais la mienne. ;) –