立即检测客户端与服务器套接字的断开连接
Posted
技术标签:
【中文标题】立即检测客户端与服务器套接字的断开连接【英文标题】:Instantly detect client disconnection from server socket 【发布时间】:2010-10-17 20:37:12 【问题描述】:如何检测到客户端与我的服务器断开连接?
我的AcceptCallBack
方法中有以下代码
static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
//Accept incoming connection
Socket listener = (Socket)ar.AsyncState;
handler = listener.EndAccept(ar);
我需要想办法尽快发现客户端已从handler
Socket 断开。
我试过了:
handler.Available;
handler.Send(new byte[1], 0,
SocketFlags.None);
handler.Receive(new byte[1], 0,
SocketFlags.None);
当您连接到服务器并想要检测服务器何时断开连接时,上述方法有效,但它们不起作用当您是服务器并想要检测客户端断开连接时。
任何帮助将不胜感激。
【问题讨论】:
@Samuel:TCP 和连接标签 与这篇文章非常相关,因为 TCP 保持连接(而其他网络协议,如 UDP 则没有)。跨度> 更多关于心跳解决方案的信息来自我的博客:Detection of Half-Open (Dropped) Connections 这里描述的解决方案对我很有效:***.com/questions/1387459/… 【参考方案1】:由于在套接字断开连接时没有可用于发出信号的事件,因此您必须以您可以接受的频率对其进行轮询。
使用此扩展方法,您可以有一个可靠的方法来检测套接字是否断开。
static class SocketExtensions
public static bool IsConnected(this Socket socket)
try
return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
catch (SocketException) return false;
【讨论】:
这行得通。谢谢。我将方法更改为 return !(socket.Available == 0 && socket.Poll(1, SelectMode.SelectRead));因为我怀疑 socket.Available 比 Socket.Poll() 快 除非连接的另一端实际关闭/关闭套接字,否则此方法不起作用。在超时期限之前不会注意到拔下的网络/电源线。立即通知断开连接的唯一方法是使用心跳功能来持续检查连接。 @Smart Alec:实际上,您应该使用上面显示的示例。如果更改顺序,可能会出现竞争条件:如果socket.Available
返回 0 并且您在 socket.Poll
被调用之前收到一个数据包,Poll
将返回 true 并且方法将返回 false
,尽管套接字实际上是仍然健康。
这在 99% 的情况下都能正常工作,但有时它会产生错误的断开连接。
就像 Matthew Finlay 注意到的那样,这有时会报告错误的断开连接,因为在 Poll 方法的结果和检查可用属性之间仍然存在竞争条件。一个数据包可能几乎准备好可以读取但还没有,因此可用为 0 - 但一毫秒后,有数据要读取。更好的选择是尝试接收一个字节和 SocketFlags.Peek 标志。或者实现某种形式的心跳并将连接状态保持在更高的级别。或者依靠您的发送/接收方法(和回调,如果使用异步版本)的错误处理。【参考方案2】:
有人提到TCP Socket的keepAlive能力。 这里描述得很好:
http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
我是这样使用它的:在套接字连接之后,我正在调用这个函数,它设置了 keepAlive。 keepAliveTime
参数指定超时,以毫秒为单位,在发送第一个保持活动数据包之前没有活动。 keepAliveInterval
参数指定在没有收到确认的情况下发送连续保持活动数据包之间的时间间隔,以毫秒为单位。
void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval)
int size = Marshal.SizeOf(new uint());
var inOptionValues = new byte[size * 3];
BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)keepAliveTime).CopyTo(inOptionValues, size);
BitConverter.GetBytes((uint)keepAliveInterval).CopyTo(inOptionValues, size * 2);
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
我也在使用异步读取:
socket.BeginReceive(packet.dataBuffer, 0, 128,
SocketFlags.None, new AsyncCallback(OnDataReceived), packet);
在回调中,这里捕获到超时SocketException
,当socket在keep-alive数据包后没有收到ACK信号时引发。
public void OnDataReceived(IAsyncResult asyn)
try
SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
int iRx = socket.EndReceive(asyn);
catch (SocketException ex)
SocketExceptionCaught(ex);
这样,我可以安全地检测 TCP 客户端和服务器之间的断开连接。
【讨论】:
是的,将 keepalive+interval 设置为较低的值,任何轮询/发送都应该在 keepalive+10*interval 毫秒后失败。自 Vista 以来,10 次重试似乎是硬编码的?与大多数其他答案不同,即使您拔下电缆也能正常工作。这应该是公认的答案。【参考方案3】:这根本不可能。您和服务器之间没有物理连接(除了极少数情况下,您使用环回电缆在两台计算机之间进行连接)。
当连接正常关闭时,会通知对方。但是,如果连接以其他方式断开连接(例如用户连接被丢弃),那么服务器将不知道直到它超时(或尝试写入连接并且 ack 超时)。这就是 TCP 的工作方式,您必须接受它。
因此,“立即”是不现实的。您可以做的最好的事情是在超时期限内,这取决于代码运行的平台。
编辑: 如果您只是在寻找优雅的连接,那么为什么不从您的客户端向服务器发送“DISCONNECT”命令呢?
【讨论】:
谢谢,但我实际上是指正常的软件断开连接,而不是物理断开连接。我正在运行 Windows 如果他只是在寻找优雅的断开连接,他不需要发送任何东西。他的阅读将返回流的结尾。【参考方案4】:“这就是 TCP 的工作方式,你必须接受它。”
是的,你是对的。这是我已经意识到的生活事实。即使在使用此协议(甚至其他协议)的专业应用程序中,您也会看到相同的行为。我什至看到它出现在网络游戏中;你是好友说“再见”,他似乎又在线了 1-2 分钟,直到服务器“打扫房间”。
您可以在此处使用建议的方法,或者按照建议实施“心跳”。我选择前者。但如果我确实选择了后者,我只需让服务器每隔一段时间用一个字节“ping”每个客户端,看看我们是否有超时或没有响应。您甚至可以使用后台线程以精确的时间来实现这一点。如果您真的担心的话,甚至可以在某种选项列表(枚举标志或其他东西)中实现组合。但是,只要您进行更新,在更新服务器时有一点延迟并不是什么大不了的事。这是互联网,没有人会想到它是魔法! :)
【讨论】:
【参考方案5】:在您的系统中实施心跳可能是一种解决方案。这只有在客户端和服务器都在您的控制之下时才有可能。您可以让 DateTime 对象跟踪从套接字接收到最后一个字节的时间。并假设在一定时间间隔内未响应的套接字丢失。这仅在您实施了心跳/自定义保持活动时才有效。
【讨论】:
【参考方案6】:我发现它非常有用,另一种解决方法!
如果您使用异步方法从网络套接字读取数据(我的意思是,使用BeginReceive
- EndReceive
方法),只要连接终止;出现以下情况之一:发送的消息没有数据(您可以通过Socket.Available
看到它 - 即使触发了BeginReceive
,它的值也将为零)或Socket.Connected
在此调用中值变为假(不要'然后不要尝试使用EndReceive
)。
我发布了我使用的函数,我想你可以更好地理解我的意思:
private void OnRecieve(IAsyncResult parameter)
Socket sock = (Socket)parameter.AsyncState;
if(!sock.Connected || sock.Available == 0)
// Connection is terminated, either by force or willingly
return;
sock.EndReceive(parameter);
sock.BeginReceive(..., ... , ... , ..., new AsyncCallback(OnRecieve), sock);
// To handle further commands sent by client.
// "..." zones might change in your code.
【讨论】:
【参考方案7】:这对我有用,关键是您需要一个单独的线程来通过轮询来分析套接字状态。在与套接字失败检测相同的线程中执行此操作。
//open or receive a server socket - TODO your code here
socket = new Socket(....);
//enable the keep alive so we can detect closure
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
//create a thread that checks every 5 seconds if the socket is still connected. TODO add your thread starting code
void MonitorSocketsForClosureWorker()
DateTime nextCheckTime = DateTime.Now.AddSeconds(5);
while (!exitSystem)
if (nextCheckTime < DateTime.Now)
try
if (socket!=null)
if(socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0)
//socket not connected, close it if it's still running
socket.Close();
socket = null;
else
//socket still connected
catch
socket.Close();
finally
nextCheckTime = DateTime.Now.AddSeconds(5);
Thread.Sleep(1000);
【讨论】:
【参考方案8】:这里的示例代码 http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx 展示了如何在不发送任何数据的情况下确定 Socket 是否仍处于连接状态。
如果您在服务器程序上调用了 Socket.BeginReceive(),然后客户端“优雅地”关闭了连接,您的接收回调将被调用并且 EndReceive() 将返回 0 字节。这些 0 字节意味着客户端“可能”已断开连接。然后,您可以使用 MSDN 示例代码中显示的技术来确定连接是否已关闭。
【讨论】:
【参考方案9】:通过mbargiel和mycelo在接受的答案上扩展cmets,以下可以与服务器端的非阻塞套接字一起使用,以通知客户端是否已关闭。
这种方法不会受到影响接受答案中 Poll 方法的竞争条件。
// Determines whether the remote end has called Shutdown
public bool HasRemoteEndShutDown
get
try
int bytesRead = socket.Receive(new byte[1], SocketFlags.Peek);
if (bytesRead == 0)
return true;
catch
// For a non-blocking socket, a SocketException with
// code 10035 (WSAEWOULDBLOCK) indicates no data available.
return false;
该方法基于这样一个事实,即Socket.Receive
方法在远程端关闭其套接字并且我们已从中读取所有数据后立即返回零。来自Socket.Receive documentation:
如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且已经接收到所有可用数据,则 Receive 方法将立即完成并返回零字节。
如果您处于非阻塞模式,并且协议栈缓冲区中没有可用数据,则 Receive 方法将立即完成并抛出 SocketException。
第二点解释了 try-catch 的必要性。
使用SocketFlags.Peek
标志不会触及任何接收到的数据,以供单独的接收机制读取。
上述内容也适用于 blocking 套接字,但请注意代码将在 Receive 调用上阻塞(直到接收到数据或接收超时,再次导致 @987654327 @)。
【讨论】:
【参考方案10】:你不能只使用 Select 吗?
在已连接的套接字上使用 select。如果选择返回您的套接字为就绪,但随后的接收返回 0 字节,这意味着客户端断开连接。 AFAIK,这是确定客户端是否断开连接的最快方法。
我不懂 C#,所以如果我的解决方案不适合 C#(尽管 C# 确实提供了 select)或者我误解了上下文,请忽略。
【讨论】:
【参考方案11】:使用 SetSocketOption 方法,您将能够设置 KeepAlive,以便在 Socket 断开连接时通知您
Socket _connectedSocket = this._sSocketEscucha.EndAccept(asyn);
_connectedSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
http://msdn.microsoft.com/en-us/library/1011kecd(v=VS.90).aspx
希望对您有所帮助! 拉米罗·里纳尔迪
【讨论】:
它不会“让您知道任何时候套接字断开连接”。如果保活计时器到期,它最终会检测它。默认情况下,它设置为两个小时。您不能将其描述为“无论何时”。您也不能将其描述为“让[ting]您知道:您仍然必须进行读取或写入才能检测到故障。 -1【参考方案12】:我有同样的问题,试试这个:
void client_handler(Socket client) // set 'KeepAlive' true
while (true)
try
if (client.Connected)
else
// client disconnected
break;
catch (Exception)
client.Poll(4000, SelectMode.SelectRead);// try to get state
【讨论】:
【参考方案13】:这是在 VB 中,但它似乎对我很有效。它像上一篇文章一样寻找一个 0 字节的返回值。
Private Sub RecData(ByVal AR As IAsyncResult)
Dim Socket As Socket = AR.AsyncState
If Socket.Connected = False And Socket.Available = False Then
Debug.Print("Detected Disconnected Socket - " + Socket.RemoteEndPoint.ToString)
Exit Sub
End If
Dim BytesRead As Int32 = Socket.EndReceive(AR)
If BytesRead = 0 Then
Debug.Print("Detected Disconnected Socket - Bytes Read = 0 - " + Socket.RemoteEndPoint.ToString)
UpdateText("Client " + Socket.RemoteEndPoint.ToString + " has disconnected from Server.")
Socket.Close()
Exit Sub
End If
Dim msg As String = System.Text.ASCIIEncoding.ASCII.GetString(ByteData)
Erase ByteData
ReDim ByteData(1024)
ClientSocket.BeginReceive(ByteData, 0, ByteData.Length, SocketFlags.None, New AsyncCallback(AddressOf RecData), ClientSocket)
UpdateText(msg)
End Sub
【讨论】:
【参考方案14】:以上答案可以总结如下:
Socket.Connected 属性根据上次读取或接收状态确定套接字状态,因此在您手动关闭连接或远程结束正常关闭套接字(关闭)之前,它无法检测当前断开状态。
所以我们可以使用下面的函数来检查连接状态:
bool IsConnected(Socket socket)
try
if (socket == null) return false;
return !((socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0) || !socket.Connected);
catch (SocketException)
return false;
//the above code is short exp to :
/* try
bool state1 = socket.Poll(5000, SelectMode.SelectRead);
bool state2 = (socket.Available == 0);
if ((state1 && state2) || !socket.Connected)
return false;
else
return true;
catch (SocketException)
return false;
*/
上面的检查也需要关心poll响应时间(block time) 也正如 Microsoft Documents 所说:这种轮询方法“无法检测到诸如网络电缆损坏或远程主机被不正常关闭之类的问题”。 同样如上所述,socket.poll 和 socket.avaiable 之间存在竞争条件,这可能会导致错误断开连接。
Microsoft Documents 所说的最佳方法是尝试发送或接收数据以检测 MS 文档所说的这些类型的错误。 以下代码来自微软文档:
// This is how you can determine whether a socket is still connected.
bool IsConnected(Socket client)
bool blockingState = client.Blocking; //save socket blocking state.
bool isConnected = true;
try
byte [] tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0); //make a nonblocking, zero-byte Send call (dummy)
//Console.WriteLine("Connected!");
catch (SocketException e)
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
//Console.WriteLine("Still Connected, but the Send would block");
else
//Console.WriteLine("Disconnected: error code 0!", e.NativeErrorCode);
isConnected = false;
finally
client.Blocking = blockingState;
//Console.WriteLine("Connected: 0", client.Connected);
return isConnected ;
//还有来自微软文档的 cmets*
socket.Connected 属性获取 Socket 在最后一次 I/O 操作时的连接状态。当它返回 false 时,Socket 要么从未连接,要么不再连接。 Connected 不是线程安全的;当 Socket 与另一个线程断开连接时,它可能会在操作中止后返回 true。 Connected 属性的值反映了最近一次操作时的连接状态。 如果您需要确定连接的当前状态,请进行非阻塞、零字节的发送调用。如果调用成功返回或抛出 WAEWOULDBLOCK 错误代码(10035),则套接字仍处于连接状态; //否则,套接字不再连接。【讨论】:
【参考方案15】:如果要轮询,还可以检查套接字的 .IsConnected 属性。
【讨论】:
这不适用于我上面介绍的任何场景(服务器/客户端) 此外,该属性称为 .Connected。 这不会检测到任意断开连接。你仍然需要做一些 I/O。 @Jay 不,它没有。 The value of theConnected
property reflects the state of the connection as of the most recent operation。没有投票。以上是关于立即检测客户端与服务器套接字的断开连接的主要内容,如果未能解决你的问题,请参考以下文章
socket.io 客户端在长 Node.js 函数中自动断开连接