ネットワークソケット(System.Net.Sockets.Socket)がまだ接続されているかどうかを確認するには、他のホストが切断時にパケットを送信しない場合(例:正常に切断されなかったため)?
As ポールターナー 回答済みSocket.Connected
は、この状況では使用できません。接続がまだアクティブかどうかを確認するには、毎回接続をポーリングする必要があります。これは私が使用したコードです:
bool SocketConnected(Socket s)
{
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if (part1 && part2)
return false;
else
return true;
}
それはこのように動作します:
s.Poll
if s.Available
読み取り可能なバイト数を返しますzendar が書いたように、Socket.Poll
およびSocket.Available
、ただし、最初にソケットが初期化されていない可能性があることを考慮する必要があります。これは最後の(私が信じる)情報であり、Socket.Connected
プロパティ。メソッドの改訂版は次のようになります。
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;
*/
}
Socket.Connected
プロパティは、ソケットが接続されているかどうかを判断します。実際には、ソケットで実行された最後の送信/受信操作のステータスが反映されます。
独自のアクション(ソケットの破棄、切断するメソッドの呼び出し)によってソケットが閉じられた場合、Socket.Connected
はfalse
を返します。ソケットが他の手段によって切断された場合、プロパティは、次に情報を送信または受信しようとするまでtrue
を返し、その時点でSocketException
またはObjectDisposedException
のいずれかが投げた。
例外が発生した後にプロパティを確認できますが、以前は信頼できません。
this MSDNの記事に基づいて拡張メソッドを作成しました。これは、ソケットがまだ接続されているかどうかを判断する方法です。
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;
}
}
ネットワークケーブルを抜くと、受け入れられた答えが機能しないようです。または、サーバーがクラッシュします。または、ルーターがクラッシュします。または、インターネット請求書の支払いを忘れた場合。信頼性を高めるためにTCPキープアライブオプションを設定します。
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);
時間値は、データが最後に送信されてからのタイムアウトを設定します。次に、キープアライブパケットの送受信を試みます。失敗すると、接続が無効であると判断する前に、指定された間隔で10回(Vista AFAIK以降にハードコードされた数)再試行します。
したがって、上記の値は2 + 10 * 1 = 12秒の検出になります。その後、すべての読み取り/書き込み/ポーリング操作はソケットで失敗します。
NibblyPig および zendar からのアドバイスに従って、私が作成したすべてのテストで機能する以下のコードを思いつきました。 pingと投票の両方が必要になりました。 pingは、ケーブルが切断されたか、物理層が中断されたか(ルーターの電源がオフになったなど)を知らせてくれます。しかし、再接続後にRSTを受け取ると、pingが正常な場合もありますが、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;
}
}
Alexander Loggerzendar sの回答で指摘したように、完全に確認するために何かを送信する必要があります。接続されたパートナーがこのソケットをまったく読み取らない場合は、次のコードを使用できます。
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;
}
}
}
ただし、その場合でも、ネットワークケーブルの破損などが検出されるまで数秒かかる場合があります。
最善の方法は、クライアントにX秒ごとにPINGを送信させ、サーバーがしばらく受信していないと切断されたと見なすことです。
ソケットを使用しているときにあなたと同じ問題が発生しましたが、これが唯一の方法でした。 socket.connectedプロパティは決して正しくありませんでした。
しかし、最終的には、ソケットよりもはるかに信頼性が高いため、WCFの使用に切り替えました。