web-dev-qa-db-fra.com

Comment se reconnecter gracieusement à un socket

J'ai une méthode suivante qui se connecte à un point final lorsque mon programme démarre

ChannelSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var remoteIpAddress = IPAddress.Parse(ChannelIp);
ChannelEndPoint = new IPEndPoint(remoteIpAddress, ChannelPort);
ChannelSocket.Connect(ChannelEndPoint);

J'ai également une minuterie configurée pour déclencher toutes les 60 secondes l'appel CheckConnectivity, qui tente d'envoyer un tableau d'octets arbitraires au point final pour s'assurer que la connexion est toujours active et, si l'envoi échoue, il tentera de se reconnecter. .

public bool CheckConnectivity(bool isReconnect)
{
    if (ChannelSocket != null)
    {
        var blockingState = ChannelSocket.Blocking;
        try
        {
            var tmp = new byte[] { 0 };
            ChannelSocket.Blocking = false;
            ChannelSocket.Send(tmp);
        }
        catch (SocketException e)
        {
            try
            {
                ReconnectChannel();
            }
            catch (Exception ex)
            {
                return false;
            }
        }
    }
    else
    {
        ConnectivityLog.Warn(string.Format("{0}:{1} is null!", ChannelIp, ChannelPort));
        return false;
    }

    return true;
} 

private void ReconnectChannel()
{
    try
    {
        ChannelSocket.Shutdown(SocketShutdown.Both);
        ChannelSocket.Disconnect(true);
        ChannelSocket.Close();
    }
    catch (Exception ex)
    {
        ConnectivityLog.Error(ex);
    }

    ChannelSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    var remoteIpAddress = IPAddress.Parse(ChannelIp);
    ChannelEndPoint = new IPEndPoint(remoteIpAddress, ChannelPort);
    ChannelSocket.Connect(ChannelEndPoint);
    Thread.Sleep(1000);

    if (ChannelSocket.Connected)
    {
        ConnectivityLog.Info(string.Format("{0}:{1} is reconnected!", ChannelIp, ChannelPort));
    }
    else
    {
        ConnectivityLog.Warn(string.Format("{0}:{1} failed to reconnect!", ChannelIp, ChannelPort));
    }
}

Je voudrais donc tester ce qui précède, pour débrancher physiquement le câble LAN de mon périphérique Ethernet, en permettant à mon code de tenter de se reconnecter (ce qui échoue évidemment) et de reconnecter le câble LAN. 

Cependant, même après avoir reconnecté le câble LAN (capable d'effectuer un ping), ChannelSocket.Connect (ChannelEndPoint) dans ma méthode de reconnexion lève toujours cette erreur

No connection could be made because the target machine actively refused it 192.168.168.160:4001

Si je dois redémarrer toute mon application, celle-ci se connecte correctement. Comment puis-je modifier ma méthode de reconnexion de sorte que je n'ai pas à redémarrer mon application pour me reconnecter à mon périphérique Ethernet?

12
Null Reference

Si une application ferme un port TCP/IP, le protocole indique que le port reste à l'état TIME_WAIT pendant une certaine durée (240 secondes par défaut sur une machine Windows) . Voir ci-dessous les références -

http://en.wikipedia.org/wiki/Transmission_Control_Protocol

http://support.Microsoft.com/kb/137984

http://www.pctools.com/guides/registry/detail/878/

Ce que cela signifie pour votre scénario - est que vous ne pouvez pas vous attendre à fermer (volontairement ou non) et à rouvrir un port dans un court laps de temps (même plusieurs secondes). Malgré quelques ajustements de registre que vous trouverez sur Internet, le port ne sera pas disponible pour les applications Windows, pendant au moins 30 secondes. (Encore une fois, default est de 240 secondes)

Vos options - ici sont limitées ...

  1. De la documentation à http://msdn.Microsoft.com/en-us/library/4xzx2d41(v=vs.110).aspx -

"Si le socket a déjà été déconnecté, vous ne pouvez pas utiliser cette méthode (Connect) pour restaurer la connexion. Utilisez l'une des méthodes asynchrones BeginConnect pour vous reconnecter. Il s'agit d'une limitation du fournisseur sous-jacent."

La raison pour laquelle la documentation suggère que BeginConnect doit être utilisé est ce que j'ai mentionné ci-dessus. Il ne s'attend tout simplement pas à pouvoir établir la connexion immédiatement .. et la seule option consiste à effectuer l'appel de manière asynchrone et pendant que vous attendez. pour que la connexion s'établisse en quelques minutes, attendez-vous à planifier et échouez. Essentiellement, probablement pas une option idéale.

  1. Si l'attente longue et l'incertitude ne sont pas acceptables, votre autre option consiste à négocier en quelque sorte un port différent entre le client et le serveur. (Par exemple, en théorie, vous pourriez utiliser UDP, connectionless, pour négocier le nouveau port TCP sur lequel vous rétabliriez la connexion). La communication utilisant UDP, en théorie bien sûr, n’est pas garantie par la conception. Mais devrait fonctionner la plupart du temps (aujourd’hui, le réseautage dans une organisation type n’est pas aussi flou/peu fiable). Subjectif au scénario/à l’opinion, peut-être mieux que l’option 1, mais plus de travail et des chances plus petites mais plus limitées de ne pas fonctionner

  2. Comme suggéré dans l’un des commentaires, c’est là que les protocoles de couche d’application tels que http et services http ont un avantage. Utilisez-les, au lieu des sockets de bas niveau, si vous pouvez .Si cela vous convient, c’est la meilleure option.

(PS - FYI - Pour HTTP, le système d’exploitation comporte de nombreuses manipulations spéciales, y compris Windows. Par exemple, il existe un pilote dédié Http.sys, spécialement conçu pour traiter plusieurs applications essayant d’écouter sur le même port 80, etc. Les détails voici un sujet pour une autre fois .. le point est, il y a beaucoup de bonté et de travail dur fait pour vous, quand il s'agit de HTTP)

23
Vikas Gupta

Peut-être devriez-vous passer à une classe d'abstraction plus élevée, qui traite mieux de tous ces petits détails astucieux?

Je vais utiliser pour ces connexions réseau les classes TcpListener et TcpClient . L'utilisation de ces classes est assez simple:

Le côté client:

public void GetInformationAsync(IPAddress ipAddress)
{
    _Log.Info("Start retrieving informations from address " + ipAddress + ".");
    var tcpClient = new TcpClient();
    tcpClient.BeginConnect(ipAddress, _PortNumber, OnTcpClientConnected, tcpClient);
}

private void OnTcpClientConnected(IAsyncResult asyncResult)
{
    try
    {
        using (var tcpClient = (TcpClient)asyncResult.AsyncState)
        {
            tcpClient.EndConnect(asyncResult);
            var ipAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
            var stream = tcpClient.GetStream();
            stream.ReadTimeout = 5000;
            _Log.Debug("Connection established to " + ipAddress + ".");

            var formatter = new BinaryFormatter();
            var information = (MyInformation)formatter.Deserialize(stream);

            _Log.Info("Successfully retrieved information from address " + ipAddress + ".");
            InformationAvailable.FireEvent(this, new InformationEventArgs(information));
        }
    }
    catch (Exception ex)
    {
        _Log.Error("Error in retrieving informations.", ex);
        return;
    }
}

Le côté serveur:

public void Start()
{
    ThrowIfDisposed();

    if (_TcpServer != null;)
        _TcpServer.Stop();

    _TcpServer = new TcpListener(IPAddress.Any, _PortNumber);
    _TcpServer.Start();

    _TcpServer.BeginAcceptTcpClient(OnClientConnected, _TcpServer);
    _Log.Info("Start listening for incoming connections on " + _TcpServer.LocalEndpoint + ".");
}

private void OnClientConnected(IAsyncResult asyncResult)
{
    var tcpServer = (TcpListener)asyncResult.AsyncState;
    IPAddress address = IPAddress.None;

    try
    {
        if (tcpServer.Server != null
            && tcpServer.Server.IsBound)
            tcpServer.BeginAcceptTcpClient(OnClientConnected, tcpServer);

        using (var client = tcpServer.EndAcceptTcpClient(asyncResult))
        {
            address = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
            _Log.Debug("Client connected from address " + address + ".");

            var formatter = new BinaryFormatter();
            var informations = new MyInformation()
            {
                // Initialize properties with desired values.
            };

            var stream = client.GetStream();
            formatter.Serialize(stream, description);

            _Log.Debug("Sucessfully serialized information into network stream.");
        }
    }
    catch (ObjectDisposedException)
    {
        // This normally happens, when the server will be stopped
        // and their exists no other reliable way to check this state
        // before calling EndAcceptTcpClient().
    }
    catch (Exception ex)
    {
        _Log.Error(String.Format("Cannot send instance information to {0}.", address), ex);
    }
}

Ce code fonctionne et ne crée aucun problème avec une connexion perdue côté client. Si vous avez perdu une connexion côté serveur, vous devez rétablir l'auditeur, mais c'est une autre histoire.

4
Oliver

Dans ReconnectChannel, supprimez simplement l'objet ChannelSocket.

try
    {
     `//ChannelSocket.Shutdown(SocketShutdown.Both);
        //ChannelSocket.Disconnect(true);
        //ChannelSocket.Close();
        ChannelSocket.Dispose();`   
    }

Cela fonctionne pour moi. Faites-moi savoir si cela ne fonctionne pas pour vous.

1