¿Cómo puedo detectar que un cliente se ha desconectado de mi servidor?
Tengo el siguiente código en mi AcceptCallBack
método
static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
//Accept incoming connection
Socket listener = (Socket)ar.AsyncState;
handler = listener.EndAccept(ar);
}
Necesito encontrar una manera de descubrir lo antes posible que el cliente se ha desconectado del handler
Socket.
He intentado:
handler.Available;
handler.Send(new byte[1], 0, SocketFlags.None);
handler.Receive(new byte[1], 0, SocketFlags.None);
Los enfoques anteriores funcionan cuando se conecta a un servidor y desea detectar cuándo se desconecta el servidor, pero no funcionan cuando usted es el servidor y desea detectar la desconexión del cliente.
Se agradecerá cualquier ayuda.
Respuestas:
Dado que no hay eventos disponibles para señalar cuando el enchufe está desconectado, deberá sondearlo a una frecuencia que sea aceptable para usted.
Con este método de extensión, puede tener un método confiable para detectar si un enchufe está desconectado.
static class SocketExtensions { public static bool IsConnected(this Socket socket) { try { return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0); } catch (SocketException) { return false; } } }
fuente
socket.Available
devuelve 0 y recibe un paquete justo antes de quesocket.Poll
se llame,Poll
devolverá verdadero y el método volveráfalse
, aunque el socket todavía está en buen estado.Alguien mencionó la capacidad keepAlive de TCP Socket. Aquí está muy bien descrito:
http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
Lo estoy usando de esta manera: después de que el socket está conectado, llamo a esta función, que activa keepAlive. El
keepAliveTime
parámetro especifica el tiempo de espera, en milisegundos, sin actividad hasta que se envía el primer paquete Keep-Alive. ElkeepAliveInterval
parámetro especifica el intervalo, en milisegundos, entre el momento en que se envían paquetes de mantenimiento de vida sucesivos si no se recibe ningún reconocimiento.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); }
También estoy usando lectura asincrónica:
socket.BeginReceive(packet.dataBuffer, 0, 128, SocketFlags.None, new AsyncCallback(OnDataReceived), packet);
Y en la devolución de llamada, aquí se captura el tiempo de espera
SocketException
, que aumenta cuando el socket no recibe la señal ACK después del paquete de mantenimiento.public void OnDataReceived(IAsyncResult asyn) { try { SocketPacket theSockId = (SocketPacket)asyn.AsyncState; int iRx = socket.EndReceive(asyn); } catch (SocketException ex) { SocketExceptionCaught(ex); } }
De esta manera, puedo detectar de forma segura la desconexión entre el cliente TCP y el servidor.
fuente
Esto simplemente no es posible. No hay conexión física entre usted y el servidor (excepto en el caso extremadamente raro en el que se conecta entre dos computadoras con un cable de bucle invertido).
Cuando la conexión se cierra correctamente, se notifica al otro lado. Pero si la conexión se desconecta de alguna otra manera (digamos que la conexión del usuario se interrumpe), el servidor no lo sabrá hasta que se agote el tiempo de espera (o intente escribir en la conexión y se agote el tiempo de confirmación). Esa es la forma en que funciona TCP y tienes que vivir con él.
Por lo tanto, "instantáneamente" no es realista. Lo mejor que puede hacer es dentro del período de tiempo de espera, que depende de la plataforma en la que se ejecuta el código.
EDITAR: Si solo está buscando conexiones elegantes, ¿por qué no enviar un comando "DESCONECTAR" al servidor desde su cliente?
fuente
"Así es como funciona TCP y tienes que vivir con él".
Sí, tienes razón. Es un hecho de la vida que me he dado cuenta. Verá el mismo comportamiento exhibido incluso en aplicaciones profesionales que utilizan este protocolo (e incluso otros). Incluso lo he visto ocurrir en juegos en línea; tu amigo dice "adiós", y parece estar en línea por otros 1-2 minutos hasta que el servidor "limpia la casa".
Puede utilizar los métodos sugeridos aquí o implementar un "latido", como también se sugiere. Elijo el primero. Pero si escogiera lo último, simplemente haría que el servidor hiciera "ping" a cada cliente de vez en cuando con un solo byte, y vería si tenemos un tiempo de espera o no hay respuesta. Incluso podría usar un hilo de fondo para lograr esto con una sincronización precisa. Tal vez incluso se podría implementar una combinación en algún tipo de lista de opciones (indicadores de enumeración o algo así) si realmente le preocupa. Pero no es tan importante tener un pequeño retraso en la actualización del servidor, siempre que lo actualice. ¡Es Internet y nadie espera que sea mágico! :)
fuente
Implementar latidos en su sistema podría ser una solución. Esto solo es posible si tanto el cliente como el servidor están bajo su control. Puede hacer que un objeto DateTime lleve un registro de la hora en que se recibieron los últimos bytes del socket. Y suponga que el socket que no respondió durante un cierto intervalo se pierde. Esto solo funcionará si ha implementado heartbeat / custom keep alive.
fuente
¡He encontrado otra solución bastante útil para eso!
Si usa métodos asincrónicos para leer datos del conector de red (quiero decir, use
BeginReceive
-EndReceive
métodos), siempre que se termine una conexión; Aparece una de estas situaciones: O se envía un mensaje sin datos (puede verlo conSocket.Available
- aunqueBeginReceive
se active, su valor será cero) o elSocket.Connected
valor se vuelve falso en esta llamada (no intente usarloEndReceive
).Estoy publicando la función que utilicé, creo que puedes ver mejor lo que quise decir:
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. }
fuente
Esto funcionó para mí, la clave es que necesita un hilo separado para analizar el estado del socket con el sondeo. hacerlo en el mismo hilo que el socket falla la detección.
//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); } }
fuente
El código de ejemplo aquí http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx muestra cómo determinar si el Socket todavía está conectado sin enviar ningún dato.
Si llamó a Socket.BeginReceive () en el programa del servidor y luego el cliente cerró la conexión "correctamente", se llamará a su devolución de llamada de recepción y EndReceive () devolverá 0 bytes. Estos 0 bytes significan que el cliente "puede" haberse desconectado. A continuación, puede utilizar la técnica que se muestra en el código de ejemplo de MSDN para determinar con certeza si la conexión se cerró.
fuente
Ampliando los comentarios de mbargiel y mycelo sobre la respuesta aceptada, lo siguiente se puede utilizar con un conector sin bloqueo en el extremo del servidor para informar si el cliente se ha apagado.
Este enfoque no sufre la condición de carrera que afecta al método Poll en la respuesta aceptada.
// 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; } }
El enfoque se basa en el hecho de que el
Socket.Receive
método devuelve cero inmediatamente después de que el extremo remoto apaga su socket y hayamos leído todos los datos. De Socket.Reciba documentación :El segundo punto explica la necesidad del try-catch.
El uso de la
SocketFlags.Peek
bandera deja intactos los datos recibidos para que los lea un mecanismo de recepción independiente.Lo anterior también funcionará con un conector de bloqueo , pero tenga en cuenta que el código se bloqueará en la llamada de recepción (hasta que se reciban los datos o transcurra el tiempo de espera de recepción, lo que nuevamente da como resultado a
SocketException
).fuente
¿No puedes usar Select?
Utilice select en un enchufe conectado. Si la selección regresa con su conector como Listo pero la recepción posterior devuelve 0 bytes, eso significa que el cliente desconectó la conexión. AFAIK, esa es la forma más rápida de determinar si el cliente se desconectó.
No sé C #, así que ignore si mi solución no encaja en C # ( aunque C # proporciona selección ) o si había entendido mal el contexto.
fuente
Usando el método SetSocketOption, podrá configurar KeepAlive que le avisará cuando un Socket se desconecte
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
¡Espero eso ayude! Ramiro Rinaldi
fuente
Tuve el mismo problema, prueba esto:
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 } } }
fuente
Esto está en VB, pero parece funcionar bien para mí. Busca un retorno de 0 bytes como la publicación anterior.
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
fuente
También puede verificar la propiedad .IsConnected del socket si fuera a sondear.
fuente
Connected
propiedad refleja el estado de la conexión a partir de la operación más reciente . No hay votación.