Détecter instantanément la déconnexion du client du socket du serveur
Comment puis-je détecter qu'un client s'est déconnecté de mon serveur?
J'ai le code suivant dans ma méthode AcceptCallBack
static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
//Accept incoming connection
Socket listener = (Socket)ar.AsyncState;
handler = listener.EndAccept(ar);
}
Je dois trouver un moyen de découvrir dès que possible que le client s'est déconnecté du Socket handler
.
J'ai essayé:
handler.Available;
handler.Send(new byte[1], 0, SocketFlags.None);
handler.Receive(new byte[1], 0, SocketFlags.None);
Les approches ci-dessus fonctionnent lorsque vous vous connectez à un serveur et que vous souhaitez détecter quand le serveur se déconnecte, mais elles ne fonctionnent pas lorsque vous êtes le serveur et souhaitez détecter client déconnexion.
Toute aide sera appréciée.
11 réponses
Comme il n'y a pas d'événements disponibles pour signaler lorsque la socket est déconnectée, vous devrez l'interroger à une fréquence acceptable pour vous.
En utilisant cette méthode d'extension, vous pouvez avoir une méthode fiable pour détecter si une socket est déconnectée.
static class SocketExtensions
{
public static bool IsConnected(this Socket socket)
{
try
{
return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
}
catch (SocketException) { return false; }
}
}
Ce n'est tout simplement pas possible. Il n'y a pas de connexion physique entre vous et le serveur (sauf dans le cas extrêmement rare où vous vous connectez entre deux ordinateurs avec un câble de bouclage).
Lorsque la connexion est fermée gracieusement, l'autre côté est averti. Mais si la connexion est déconnectée d'une autre manière (disons que la connexion des utilisateurs est supprimée), le serveur ne le saura pas jusqu'à ce qu'il expire (ou essaie d'écrire dans la connexion et l'ACK expire). C'est juste la façon dont TCP fonctionne et vous devez vivre avec.
Par conséquent, "instantanément" est irréaliste. Le mieux que vous pouvez faire est dans le délai d'attente, qui dépend de la plate-forme sur laquelle le code s'exécute.
Modifier: Si vous cherchez seulement des connexions gracieuses, alors pourquoi ne pas simplement envoyer une commande "déconnecter" au serveur à partir de votre client?
Quelqu'un a mentionné la capacité keepAlive du socket TCP. Ici, il est bien décrit:
Http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
Je l'utilise de cette façon: une fois la socket connectée, j'appelle cette fonction, qui active keepAlive. Le paramètre keepAliveTime
spécifie le délai d'attente, en millisecondes, sans activité jusqu'à ce que le premier paquet keep-alive soit envoyé. Le paramètre keepAliveInterval
spécifie l'intervalle, en millisecondes, entre les paquets keep-alive sont envoyés si aucun accusé de réception n'est reçu.
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)time).CopyTo(inOptionValues, size);
BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, size * 2);
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
}
J'utilise aussi la lecture synchrone:
socket.BeginReceive(packet.dataBuffer, 0, 128,
SocketFlags.None, new AsyncCallback(OnDataReceived), packet);
Et dans le rappel, voici le délai d'attente pris SocketException
, qui augmente lorsque le socket n'obtient pas de signal ACK après le paquet keep-alive.
public void OnDataReceived(IAsyncResult asyn)
{
try
{
SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
int iRx = socket.EndReceive(asyn);
catch (SocketException ex)
{
SocketExceptionCaught(ex);
}
}
De cette façon, je suis capable de détecter en toute sécurité la déconnexion entre le client TCP et le serveur.
" C'est juste la façon dont TCP fonctionne et vous devez vivre avec."
Ouais, vous avez raison. C'est un fait de la vie, je suis venu à réaliser. Vous verrez le même comportement exposé même dans les applications professionnelles utilisant ce protocole (et même d'autres). Je l'ai même vu se produire dans les jeux en ligne; vous êtes copain dit "au revoir", et il semble être en ligne pendant encore 1-2 minutes jusqu'à ce que le serveur "nettoie la maison".
Vous pouvez utiliser les méthodes suggérées ici, ou implémenter un "heartbeat", comme également suggéré. Je choisis le premier. Mais si je choisissais ce dernier, j'aurais simplement le serveur "ping" chaque client de temps en temps avec un seul octet, et voir si nous avons un délai d'attente ou pas de réponse. Vous pouvez même utiliser un thread d'arrière-plan pour y parvenir avec un timing précis. Peut-être même une combinaison pourrait être implémentée dans une sorte de liste d'options (drapeaux enum ou quelque chose) si vous êtes vraiment inquiet à ce sujet. Mais ce n'est pas si grave d'avoir un peu de retard dans la mise à jour du serveur, aussi longtemps que vous le faites mettre. C'est internet, et personne ne s'attend à ce que ce soit magique! :)
Implémenter heartbeat dans votre système pourrait être une solution. Cela n'est possible que si le client et le serveur sont sous votre contrôle. Vous pouvez avoir un objet DateTime gardant une trace de l'heure à laquelle les derniers octets ont été reçus du socket. Et supposons que le socket pas répondu sur un certain intervalle sont perdus. Cela ne fonctionnera que si vous avez implémenté heartbeat/custom keep alive.
J'ai trouvé très utile, une autre solution de contournement pour cela!
Si vous utilisez des méthodes asynchrones pour lire les données du socket réseau (je veux dire, utilisez BeginReceive
- EndReceive
une de ces situations apparaît: soit un message est envoyé sans données (vous pouvez le voir avec Socket.Available
- même si BeginReceive
est déclenché, sa valeur sera zéro) ou Socket.Connected
la valeur devient fausse dans cet appel (n'essayez pas d'utiliser EndReceive
alors).
Je poste la fonction que j'ai utilisée, Je pensez que vous pouvez voir ce que je voulais dire de mieux:
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.
}
Cela a fonctionné pour moi, la clé est vous avez besoin d'un thread séparé pour analyser l'état du socket avec interrogation. le faire dans le même thread que le socket échoue à la détection.
//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);
}
}
Ne pouvez - vous pas simplement utiliser Select?
Utilisez select sur une prise connectée. Si le Select retourne avec votre socket comme prêt mais la réception suivante renvoie 0 octets, cela signifie que le client a déconnecté la connexion. AFAIK, c'est le moyen le plus rapide de déterminer si le client s'est déconnecté.
Je ne connais pas C# alors ignorez simplement si ma solution ne rentre pas dans C# (C# fournit select cependant) ou si j'avais mal compris le contexte.
L'exemple de code ici http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx montre comment déterminer si la Prise est toujours connecté sans envoyer de données.
Si vous avez appelé Socket.BeginReceive() sur le programme serveur, puis le client a fermé la connexion "gracieusement", votre callback receive sera appelé et EndReceive() retournera 0 octets. Ces 0 octets signifient que le client "peut" s'être déconnecté. Vous pouvez ensuite utiliser la technique montré dans L'exemple de code MSDN pour déterminer avec certitude si la connexion a été fermée.
En utilisant la méthode SetSocketOption, vous serez en mesure de définir KeepAlive qui vous permettra de savoir chaque fois qu'une Socket est déconnectée
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
J'espère que ça aide! Ramiro Rinaldi
Vous pouvez également vérifier la .IsConnected propriété du socket si vous deviez interroger.