J'essaie de créer un serveur UDP qui peut envoyer des messages à tous les clients qui lui en envoient. La situation réelle est un peu plus complexe, mais le plus simple est de l'imaginer comme un serveur de chat : tous ceux qui ont envoyé un message auparavant reçoivent tous les messages envoyés par d'autres clients.
Tout cela se fait par l'intermédiaire de UdpClient
dans des processus distincts. (Toutes les connexions réseau se font au sein du même système, donc je n'ai pas penser le manque de fiabilité de l'UDP est un problème ici).
Le code du serveur est une boucle comme celle-ci (le code complet est présenté plus loin) :
var udpClient = new UdpClient(9001);
while (true)
{
var packet = await udpClient.ReceiveAsync();
// Send to clients who've previously sent messages here
}
Le code du client est également simple - encore une fois, il est légèrement abrégé, mais le code complet sera présenté plus tard :
var client = new UdpClient();
client.Connect("127.0.0.1", 9001);
await client.SendAsync(Encoding.UTF8.GetBytes("Hello"));
await Task.Delay(TimeSpan.FromSeconds(15));
await client.SendAsync(Encoding.UTF8.GetBytes("Goodbye"));
client.Close();
Tout se passe bien jusqu'à ce que l'un des clients ferme son UdpClient
(ou que le processus se termine).
La prochaine fois qu'un autre client enverra un message, le serveur essaiera de le propager au client d'origine, désormais fermé. Le serveur SendAsync
n'échoue pas - mais ensuite, lorsque le serveur revient à l'appel de ReceiveAsync
, que échoue avec une exception, et je n'ai pas trouvé le moyen de la récupérer.
Si je n'envoie jamais de message au client déconnecté, je ne vois jamais le problème. Sur cette base, j'ai également créé un repro "échoue immédiatement" qui envoie simplement un message à un point de terminaison supposé ne pas être à l'écoute, puis tente de le recevoir. Cela échoue avec la même exception.
Exception :
System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.CreateException(SocketError error, Boolean forAsyncThrow)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ReceiveFromAsync(Socket socket, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.ReceiveFromAsync(Memory`1 buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.ReceiveFromAsync(ArraySegment`1 buffer, SocketFlags socketFlags, EndPoint remoteEndPoint)
at System.Net.Sockets.UdpClient.ReceiveAsync()
at Program.<Main>$(String[] args) in [...]
L'environnement :
- Windows 11, x64
- NET 6
S'agit-il d'un comportement attendu ? Est-ce que j'utilise UdpClient
incorrectement du côté du serveur ? Je veux bien que les clients ne reçoivent pas de messages une fois qu'ils ont fermé leur UdpClient
(c'est normal), et dans l'application "complète", je mettrai de l'ordre dans mon état interne pour garder une trace des clients "actifs" (qui ont envoyé des paquets récemment), mais je ne veux pas qu'un client ferme un fichier UdpClient
pour faire tomber tout le serveur. Exécutez le serveur dans une console et le client dans une autre. Une fois que le client a terminé, lancez-le à nouveau (de manière à ce qu'il essaie d'envoyer des données au client original, qui n'existe plus).
Le modèle de projet par défaut de l'application console .NET 6 convient à tous les projets.
Code de reproduction
L'exemple le plus simple est présenté en premier, mais si vous souhaitez exécuter un serveur et un client, ils sont affichés ensuite.
Serveur délibérément cassé (échec immédiat)
En partant du principe que c'est bien l'envoi qui est à l'origine du problème, il est facile de le reproduire en quelques lignes :
using System.Net;
using System.Net.Sockets;
var badEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12346);
var udpClient = new UdpClient(12345);
await udpClient.SendAsync(new byte[10], badEndpoint);
await udpClient.ReceiveAsync();
Serveur
using System.Net;
using System.Net.Sockets;
using System.Text;
var udpClient = new UdpClient(9001);
var endpoints = new HashSet<IPEndPoint>();
try
{
while (true)
{
Log($"{DateTime.UtcNow:HH:mm:ss.fff}: Waiting to receive packet");
var packet = await udpClient.ReceiveAsync();
var buffer = packet.Buffer;
var clientEndpoint = packet.RemoteEndPoint;
endpoints.Add(clientEndpoint);
Log($"Received {buffer.Length} bytes from {clientEndpoint}: {Encoding.UTF8.GetString(buffer)}");
foreach (var otherEndpoint in endpoints)
{
if (!otherEndpoint.Equals(clientEndpoint))
{
await udpClient.SendAsync(buffer, otherEndpoint);
}
}
}
}
catch (Exception e)
{
Log($"Failed: {e}");
}
void Log(string message) =>
Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss.fff}: {message}");
Client
(J'avais auparavant une boucle qui recevait les paquets envoyés par le serveur, mais cela ne semble pas faire de différence, et je l'ai donc supprimée pour des raisons de simplicité).
using System.Net.Sockets;
using System.Text;
Guid clientId = Guid.NewGuid();
var client = new UdpClient();
Log("Connecting UdpClient");
client.Connect("127.0.0.1", 9001);
await client.SendAsync(Encoding.UTF8.GetBytes($"Hello from {clientId}"));
await Task.Delay(TimeSpan.FromSeconds(15));
await client.SendAsync(Encoding.UTF8.GetBytes($"Goodbye from {clientId}"));
client.Close();
Log("UdpClient closed");
void Log(string message) =>
Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss.fff}: {message}");