Wie können Sie überprüfen, ob ein Netzwerk-Socket (System.Net.Sockets.Socket) noch angeschlossen ist, wenn der andere Host Ihnen kein Paket sendet, wenn er die Verbindung trennt (z. B. weil er die Verbindung nicht richtig hergestellt hat)?
Als Paul Turner antwortete Socket.Connected
kann in dieser Situation nicht verwendet werden. Sie müssen die Verbindung jedes Mal abfragen, um festzustellen, ob die Verbindung noch aktiv ist. Dies ist der Code, den ich verwendet habe:
bool SocketConnected(Socket s)
{
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if (part1 && part2)
return false;
else
return true;
}
Es funktioniert so:
s.Poll
gibt true zurück, wenn s.Available
gibt die Anzahl der zum Lesen verfügbaren Bytes zurückWie zendar schrieb, ist es nett, Socket.Poll
und Socket.Available
zu verwenden, aber Sie müssen berücksichtigen, dass der Socket möglicherweise nicht initialisiert wurde. Dies ist die letzte (glaube ich) Information, die von der Socket.Connected
-Eigenschaft bereitgestellt wird. Die überarbeitete Version der Methode würde ungefähr so aussehen:
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;
*/
}
Die Eigenschaft Socket.Connected
sagt Ihnen, ob ein Socket denkt, dass er verbunden ist. Sie spiegelt tatsächlich den Status der letzten Sende-/Empfangsoperation wider, die an dem Socket ausgeführt wurde.
Wenn der Socket durch Ihre eigenen Aktionen geschlossen wurde (Entsorgen des Sockets, Aufrufen von Methoden zum Trennen der Verbindung), gibt Socket.Connected
false
zurück. Wenn der Socket auf andere Weise getrennt wurde, gibt die Eigenschaft true
zurück, bis Sie das nächste Mal versuchen, Informationen zu senden oder zu empfangen. Dann wird entweder eine SocketException
oder ObjectDisposedException
geworfen.
Sie können die Eigenschaft überprüfen, nachdem die Ausnahmebedingung aufgetreten ist, sie ist jedoch zuvor nicht zuverlässig.
Die akzeptierte Antwort scheint nicht zu funktionieren, wenn Sie das Netzwerkkabel abziehen. Oder der Server stürzt ab. Oder Ihr Router stürzt ab. Oder wenn Sie vergessen, Ihre Internetrechnung zu bezahlen. Legen Sie die Keep-Alive-Optionen TCP für eine höhere Zuverlässigkeit fest.
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);
Der Zeitwert legt das Zeitlimit seit dem letzten Senden der Daten fest. Dann versucht es, ein Keep-Alive-Paket zu senden und zu empfangen. Wenn dies fehlschlägt, wiederholt es 10 Mal (Nummer seit Vista AFAIK fest codiert) in dem angegebenen Intervall, bevor entschieden wird, dass die Verbindung nicht aktiv ist.
Die obigen Werte würden also zu einer Erkennung von 2 + 10 * 1 = 12 Sekunden führen. Danach sollten alle Lese-, Schreib- und Abfragevorgänge auf dem Socket fehlschlagen.
Ich habe eine Erweiterungsmethode basierend auf this MSDN-Artikel erstellt.
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;
}
}
Dem Ratschlag von NibblyPig und zendar folgend, habe ich den folgenden Code gefunden, der bei jedem von mir durchgeführten Test funktioniert. Am Ende brauchte ich sowohl den Ping als auch die Umfrage. Der Ping informiert mich, wenn das Kabel getrennt wurde oder die physikalische Schicht anderweitig unterbrochen wurde (Router ausgeschaltet usw.). Aber manchmal bekomme ich nach dem Verbindungsaufbau ein RST, der Ping ist in Ordnung, der TCP-Status jedoch nicht.
#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;
}
}
Der beste Weg ist einfach, dass Ihr Client alle X Sekunden einen PING sendet und der Server davon ausgeht, dass die Verbindung unterbrochen wird, nachdem er eine Zeit lang keine Verbindung erhalten hat.
Ich bin bei der Verwendung von Sockets auf dasselbe Problem gestoßen, und dies war die einzige Möglichkeit, dies zu tun. Die Eigenschaft socket.connected war nie korrekt.
Am Ende habe ich jedoch auf WCF umgestellt, weil es viel zuverlässiger war als Steckdosen.
Wie Alexander Logger in der Antwort von zendar wies, muss man etwas senden, um ganz sicher zu sein. Wenn Ihr verbundener Partner überhaupt über diesen Socket liest, können Sie folgenden Code verwenden.
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;
}
}
}
Aber selbst dann kann es einige Sekunden dauern, bis ein defektes Netzwerkkabel oder ähnliches erkannt wird.