Comment vérifier si un socket réseau (System.Net.Sockets.Socket) est toujours connecté si l'autre hôte ne vous envoie pas de paquet lorsqu'il se déconnecte (par exemple, parce qu'il s'est déconnecté de manière incongrue)?
En tant que Paul Turner answer Socket.Connected
ne peut pas être utilisé dans cette situation. Vous devez interroger la connexion à chaque fois pour voir si la connexion est toujours active. C'est le code que j'ai utilisé:
bool SocketConnected(Socket s)
{
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if (part1 && part2)
return false;
else
return true;
}
Cela fonctionne comme ceci:
s.Poll
renvoie true si s.Available
renvoie le nombre d'octets disponibles pour la lecture.Comme zendar a écrit, il est agréable d’utiliser les Socket.Poll
et Socket.Available
, mais vous devez prendre en compte le fait que le socket n’a peut-être pas été initialisé. C'est la dernière information (je crois) qui est fournie par la propriété Socket.Connected
. La version révisée de la méthode ressemblerait à ceci:
static bool IsSocketConnected(Socket s)
{
return !((s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)) || !s.Connected);
/* The long, but simpler-to-understand version:
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if ((part1 && part2 ) || !s.Connected)
return false;
else
return true;
*/
}
La propriété Socket.Connected
vous dira si un socket pense qu’il est connecté. Il reflète en fait l'état de la dernière opération d'envoi/réception effectuée sur le socket.
Si le socket a été fermé par vos propres actions (en disposer, appeler des méthodes pour se déconnecter), Socket.Connected
renverra false
. Si le socket a été déconnecté par un autre moyen, la propriété retournera true
jusqu'à la prochaine tentative d'envoi ou de réception d'informations. À ce stade, une SocketException
ou ObjectDisposedException
sera renvoyée.
Vous pouvez vérifier la propriété après que l'exception se soit produite, mais elle n'est pas fiable auparavant.
La réponse acceptée ne semble pas fonctionner si vous débranchez le câble réseau. Ou le serveur tombe en panne. Ou votre routeur tombe en panne. Ou si vous oubliez de payer votre facture internet. Définissez les options de maintien en activité TCP pour une meilleure fiabilité.
public static class SocketExtensions
{
public static void SetSocketKeepAliveValues(this Socket instance, int KeepAliveTime, int KeepAliveInterval)
{
//KeepAliveTime: default value is 2hr
//KeepAliveInterval: default value is 1s and Detect 5 times
//the native structure
//struct tcp_keepalive {
//ULONG onoff;
//ULONG keepalivetime;
//ULONG keepaliveinterval;
//};
int size = Marshal.SizeOf(new uint());
byte[] inOptionValues = new byte[size * 3]; // 4 * 3 = 12
bool OnOff = true;
BitConverter.GetBytes((uint)(OnOff ? 1 : 0)).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)KeepAliveTime).CopyTo(inOptionValues, size);
BitConverter.GetBytes((uint)KeepAliveInterval).CopyTo(inOptionValues, size * 2);
instance.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
}
}
// ...
Socket sock;
sock.SetSocketKeepAliveValues(2000, 1000);
La valeur time définit le délai d'attente depuis la dernière transmission des données. Il tente ensuite d'envoyer et de recevoir un paquet persistant. En cas d'échec, il tente 10 fois (nombre codé en dur depuis Vista AFAIK) dans l'intervalle spécifié avant de décider que la connexion est morte.
Les valeurs ci-dessus entraîneraient donc une détection de 2 + 10 * 1 = 12 secondes. Après cela, toute opération de lecture/écriture/interrogation devrait échouer sur le socket.
J'ai créé une méthode d'extension basée sur this MSDN article ..__ C'est comment vous pouvez déterminer si un socket est toujours connecté.
public static bool IsConnected(this Socket client)
{
bool blockingState = client.Blocking;
try
{
byte[] tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
return true;
}
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
{
return true;
}
else
{
return false;
}
}
finally
{
client.Blocking = blockingState;
}
}
Suivant les conseils de NibblyPig et zendar , j’ai trouvé le code ci-dessous, qui fonctionne pour tous les tests que j’ai faits. J'ai fini par avoir besoin du ping et du sondage. Le ping me permet de savoir si le câble a été déconnecté ou si la couche physique a été perturbée (routeur mis hors tension, etc.). Mais parfois, après avoir reconnecté, je reçois un RST, le ping est ok, mais pas l’état TCP.
#region CHECKS THE SOCKET'S HEALTH
if (_tcpClient.Client.Connected)
{
//Do a ping test to see if the server is reachable
try
{
Ping pingTest = new Ping()
PingReply reply = pingTest.Send(ServeripAddress);
if (reply.Status != IPStatus.Success) ConnectionState = false;
} catch (PingException) { ConnectionState = false; }
//See if the tcp state is ok
if (_tcpClient.Client.Poll(5000, SelectMode.SelectRead) && (_tcpClient.Client.Available == 0))
{
ConnectionState = false;
}
}
}
else { ConnectionState = false; }
#endregion
public static class SocketExtensions
{
private const int BytesPerLong = 4; // 32 / 8
private const int BitsPerByte = 8;
public static bool IsConnected(this Socket socket)
{
try
{
return !(socket.Poll(1000, SelectMode.SelectRead) && socket.Available == 0);
}
catch (SocketException)
{
return false;
}
}
/// <summary>
/// Sets the keep-alive interval for the socket.
/// </summary>
/// <param name="socket">The socket.</param>
/// <param name="time">Time between two keep alive "pings".</param>
/// <param name="interval">Time between two keep alive "pings" when first one fails.</param>
/// <returns>If the keep alive infos were succefully modified.</returns>
public static bool SetKeepAlive(this Socket socket, ulong time, ulong interval)
{
try
{
// Array to hold input values.
var input = new[]
{
(time == 0 || interval == 0) ? 0UL : 1UL, // on or off
time,
interval
};
// Pack input into byte struct.
byte[] inValue = new byte[3 * BytesPerLong];
for (int i = 0; i < input.Length; i++)
{
inValue[i * BytesPerLong + 3] = (byte)(input[i] >> ((BytesPerLong - 1) * BitsPerByte) & 0xff);
inValue[i * BytesPerLong + 2] = (byte)(input[i] >> ((BytesPerLong - 2) * BitsPerByte) & 0xff);
inValue[i * BytesPerLong + 1] = (byte)(input[i] >> ((BytesPerLong - 3) * BitsPerByte) & 0xff);
inValue[i * BytesPerLong + 0] = (byte)(input[i] >> ((BytesPerLong - 4) * BitsPerByte) & 0xff);
}
// Create bytestruct for result (bytes pending on server socket).
byte[] outValue = BitConverter.GetBytes(0);
// Write SIO_VALS to Socket IOControl.
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
socket.IOControl(IOControlCode.KeepAliveValues, inValue, outValue);
}
catch (SocketException)
{
return false;
}
return true;
}
}
La meilleure solution consiste simplement à ce que votre client envoie une PING toutes les X secondes et au serveur de supposer qu'il est déconnecté après ne pas en avoir reçu pendant un certain temps.
J'ai rencontré le même problème que vous lorsque vous utilisiez des sockets, et c'était le seul moyen de le faire. La propriété socket.connected n'a jamais été correcte.
En fin de compte, cependant, je suis passé à l’utilisation de WCF, qui était beaucoup plus fiable que les sockets.
Comme Alexander Logger a souligné dans zendar s, vous devez envoyer quelque chose pour être complètement sûr. Si votre partenaire connecté lit sur cette socket, vous pouvez utiliser le code suivant.
bool SocketConnected(Socket s)
{
// Exit if socket is null
if (s == null)
return false;
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if (part1 && part2)
return false;
else
{
try
{
int sentBytesCount = s.Send(new byte[1], 1, 0);
return sentBytesCount == 1;
}
catch
{
return false;
}
}
}
Mais même dans ce cas, cela peut prendre quelques secondes avant qu’un câble réseau cassé ou quelque chose de similaire soit détecté.