正如Paul Turner所回答的那样,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
返回 true 如果
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
。
您可以在异常发生后检查该属性,但在此之前是不可靠的。
如果您拔掉网络电缆,或服务器崩溃,或路由器崩溃,或者忘记支付互联网账单,接受的答案似乎不起作用。设置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);
时间值设置了自数据上次发送以来的超时时间。接着它会尝试发送和接收一个保持连接(keep-alive)数据包。如果失败了,则在指定的间隔内重试10次(从Vista开始硬编码的数字)。如果这些尝试都失败了,则认为连接已经失效。
因此,以上值将导致2+10*1 = 12秒的检测时间。在此之后,任何对该套接字的读/写/轮询操作都应该失败。
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;
}
}
我基于这篇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;
}
}
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;
}
}
}
即使如此,直到检测到网络电缆断裂或类似故障,可能仍需要几秒钟的时间。
根据NibblyPig和zendar的建议,我编写了以下代码,在我进行的每个测试中都能正常工作。最终我需要使用ping和poll两个命令。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
最好的方法就是让客户端每隔X秒发送一个PING,服务器在一段时间内没有收到PING后就假定客户端已经断开连接。
当我使用套接字时,我遇到了与你相同的问题,这是我唯一能做的方法。套接字的connected属性从来都不正确。
最终,我转而使用WCF,因为它比套接字更可靠。
就像@toster-cx所说,只需使用KeepAlive,并使用Socket Connected状态来检查Socket是否仍然连接。将接收超时设置为与keepalive相同的超时时间。如果有更多问题,我很乐意帮助!
return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0)
的意思是:如果在等待时间内有可读数据,则返回 true,否则返回 false。 - kasperhjPoll
方法的 MSDN 页面:该方法无法检测某些连接问题,例如断开的网络电缆或远程主机意外关闭。您必须尝试发送或接收数据以检测这些错误。 - some_engineer