Comment configurer le délai de connexion au socket

104

Lorsque le Client tente de se connecter à une adresse IP déconnectée, il y a un long timeout de plus de 15 secondes ... Comment réduire ce timeout? Quelle est la méthode pour le configurer?

Le code que j'utilise pour configurer une connexion socket est le suivant:

try
{
    m_clientSocket = new Socket(
         AddressFamily.InterNetwork,
         SocketType.Stream,
         ProtocolType.Tcp);

    IPAddress ip = IPAddress.Parse(serverIp);
    int iPortNo = System.Convert.ToInt16(serverPort);
    IPEndPoint ipEnd = new IPEndPoint(ip, iPortNo);

    m_clientSocket.Connect(ipEnd);
    if (m_clientSocket.Connected)
    {
        lb_connectStatus.Text = "Connection Established";
        WaitForServerData();
    }
}
catch (SocketException se)
{
    lb_connectStatus.Text = "Connection Failed";
    MessageBox.Show(se.Message);
}
ninikin
la source

Réponses:

146

J'ai trouvé ça. Plus simple que la réponse acceptée et fonctionne avec .NET v2

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

// Connect using a timeout (5 seconds)

IAsyncResult result = socket.BeginConnect( sIP, iPort, null, null );

bool success = result.AsyncWaitHandle.WaitOne( 5000, true );

if ( socket.Connected )
{
    socket.EndConnect( result );
}
else 
{
     // NOTE, MUST CLOSE THE SOCKET

     socket.Close();
     throw new ApplicationException("Failed to connect server.");
}

//... 
FlappySocks
la source
20
OK, peu de commentaires sur celui-ci - j'aime cela et c'est beaucoup moins de code ... mais le succès n'est pas la condition correcte. Au lieu de cela, ajoutez if (! _Socket.Connected) et cela fonctionne beaucoup mieux. Je vais lui donner un +1 car le moins est plus d'aspect.
TravisWhidden le
2
Dépend de vos deux points finaux. S'ils sont tous les deux dans un centre de données, alors 1 seconde devrait suffire, 3 pour faire bonne mesure, 10 pour être gentil. Si une extrémité est sur un appareil mobile, tel qu'un smartphone, alors vous regardez peut-être 30 secondes.
FlappySocks
3
Une autre chose aussi à surveiller ... Si au lieu de mettre nullpour le callbacket vous prévoyez de le faire EndConnect(), si le socket a été closedalors cela vous donnera une exception. Alors assurez-vous de vérifier ...
poy
9
Que faire si je veux AUGMENTER le délai plutôt que le DIMINUER? Je pense que l'approche asynchrone vous permet simplement de faire en sorte que le code n'attende pas 20 secondes (le délai d'expiration interne défini dans socket connect). Mais dans le cas où la connexion prend plus de temps, BeginConnect s'arrêtera de toute façon. Ou, BeginConnect attend-il en interne pour toujours? J'ai une connexion très lente lorsqu'il faut parfois jusqu'à 30 à 40 secondes pour se connecter, et des délais d'attente à la 21e seconde se produisent très souvent.
Alex
3
@TravisWhidden Peut confirmer, c'est très important! D'après mon expérience, si le point final peut être atteint, mais qu'il n'y a pas de serveur sur le point final capable de recevoir la connexion, alors AsyncWaitHandle.WaitOnesera signalé, mais le socket restera non connecté.
Nicholas Miller
29

Ma prise:

public static class SocketExtensions
{
    /// <summary>
    /// Connects the specified socket.
    /// </summary>
    /// <param name="socket">The socket.</param>
    /// <param name="endpoint">The IP endpoint.</param>
    /// <param name="timeout">The timeout.</param>
    public static void Connect(this Socket socket, EndPoint endpoint, TimeSpan timeout)
    {
        var result = socket.BeginConnect(endpoint, null, null);

        bool success = result.AsyncWaitHandle.WaitOne(timeout, true);
        if (success)
        {
            socket.EndConnect(result);
        }
        else
        {
            socket.Close();
            throw new SocketException(10060); // Connection timed out.
        }
    }
}
bevacqua
la source
J'ai pris la liberté de gérer une condition. J'espère que cela ne vous dérange pas.
Hemant
Par commentaires sur la réponse la mieux notée, dont cela semble être une copie, sauf qu'elle est transformée en un SocketExtension, vous n'avez toujours pas utilisé .Connectedpour voir si vous l'êtes, et vous n'utilisez pas socket.Connected = true;pour définir success.
vapcguy
22

Je viens d'écrire une classe d'extension afin de permettre des délais d'attente dans les connexions. Utilisez-le exactement comme vous utiliseriez les Connect()méthodes standard , avec un paramètre supplémentaire nommé timeout.

using System;
using System.Net;
using System.Net.Sockets;

/// <summary>
/// Extensions to Socket class
/// </summary>
public static class SocketExtensions
{
    /// <summary>
    /// Connects the specified socket.
    /// </summary>
    /// <param name="socket">The socket.</param>
    /// <param name="host">The host.</param>
    /// <param name="port">The port.</param>
    /// <param name="timeout">The timeout.</param>
    public static void Connect(this Socket socket, string host, int port, TimeSpan timeout)
    {
        AsyncConnect(socket, (s, a, o) => s.BeginConnect(host, port, a, o), timeout);
    }

    /// <summary>
    /// Connects the specified socket.
    /// </summary>
    /// <param name="socket">The socket.</param>
    /// <param name="addresses">The addresses.</param>
    /// <param name="port">The port.</param>
    /// <param name="timeout">The timeout.</param>
    public static void Connect(this Socket socket, IPAddress[] addresses, int port, TimeSpan timeout)
    {
        AsyncConnect(socket, (s, a, o) => s.BeginConnect(addresses, port, a, o), timeout);
    }

    /// <summary>
    /// Asyncs the connect.
    /// </summary>
    /// <param name="socket">The socket.</param>
    /// <param name="connect">The connect.</param>
    /// <param name="timeout">The timeout.</param>
    private static void AsyncConnect(Socket socket, Func<Socket, AsyncCallback, object, IAsyncResult> connect, TimeSpan timeout)
    {
        var asyncResult = connect(socket, null, null);
        if (!asyncResult.AsyncWaitHandle.WaitOne(timeout))
        {
            try
            {
                socket.EndConnect(asyncResult);
            }
            catch (SocketException)
            { }
            catch (ObjectDisposedException)
            { }
        }
    }
picrap
la source
8
Fan de GhostDoc, c'est ça? ;-) "Asynchronise la connexion" - GhostDoc WTFness classique.
KeithS
1
:) oui, et parfois même pas un lecteur de ce qui a été généré.
picrap
Mieux vaut socket.EndConnect que socket.Close?
Kiquenet
3
Il socket.EndConnectfaut environ 10 secondes pour se fermer, donc la fonction retourne non pas après la période mais après la période + heure de fin de
connexion
8

Je ne programme pas en C # mais en C, nous résolvons le même problème en rendant le socket non bloquant puis en plaçant le fd dans une boucle select / poll avec une valeur de timeout égale au temps que nous sommes prêts à attendre la connexion réussir.

J'ai trouvé cela pour Visual C ++ et l'explication se penche également vers le mécanisme de sélection / interrogation que j'ai expliqué précédemment.

D'après mon expérience, vous ne pouvez pas modifier les valeurs de délai de connexion par socket. Vous le changez pour tous (en réglant les paramètres du système d'exploitation).

Aditya Sehgal
la source
7

il est peut-être trop tard mais il existe une solution intéressante basée sur Task.WaitAny (c # 5 +):

 public static bool ConnectWithTimeout(this Socket socket, string host, int port, int timeout)
        {
            bool connected = false;
            Task result = socket.ConnectAsync(host, port);               
            int index = Task.WaitAny(new[] { result }, timeout);
            connected = socket.Connected;
            if (!connected) {
              socket.Close();
            }

            return connected;
        }
Oleg Bondarenko
la source
Une surcharge "ConnectAsync" accepte-t-elle l'hôte et le port?
marsh-wiggle
@ marsh-wiggle, la méthode "ConnectAsync" a 4 surcharges docs.microsoft.com/en-us/dotnet/api/… Veuillez consulter la section Méthodes d'extension
Oleg Bondarenko
1
@OlegBondarenko ok, non disponible pour .net 4.5.1. Je dois l'envelopper moi-même. Merci!
marsh-wiggle
5

J'ai résolu le problème en utilisant la méthode Socket.ConnectAsync au lieu de la méthode Socket.Connect. Après avoir appelé Socket.ConnectAsync (SocketAsyncEventArgs), démarrez un minuteur (timer_connection), si le temps est écoulé, vérifiez si la connexion de socket est connectée (si (m_clientSocket.Connected)), sinon, affichez une erreur de temporisation.

private void connect(string ipAdd,string port)
    {
        try
        {
            SocketAsyncEventArgs e=new SocketAsyncEventArgs();


            m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPAddress ip = IPAddress.Parse(serverIp);
            int iPortNo = System.Convert.ToInt16(serverPort);
            IPEndPoint ipEnd = new IPEndPoint(ip, iPortNo);

            //m_clientSocket.
            e.RemoteEndPoint = ipEnd;
            e.UserToken = m_clientSocket;
            e.Completed+=new EventHandler<SocketAsyncEventArgs>(e_Completed);                
            m_clientSocket.ConnectAsync(e);

            if (timer_connection != null)
            {
                timer_connection.Dispose();
            }
            else
            {
                timer_connection = new Timer();
            }
            timer_connection.Interval = 2000;
            timer_connection.Tick+=new EventHandler(timer_connection_Tick);
            timer_connection.Start();
        }
        catch (SocketException se)
        {
            lb_connectStatus.Text = "Connection Failed";
            MessageBox.Show(se.Message);
        }
    }
private void e_Completed(object sender,SocketAsyncEventArgs e)
    {
        lb_connectStatus.Text = "Connection Established";
        WaitForServerData();
    }
    private void timer_connection_Tick(object sender, EventArgs e)
    {
        if (!m_clientSocket.Connected)
        {
            MessageBox.Show("Connection Timeout");
            //m_clientSocket = null;

            timer_connection.Stop();
        }
    }
ninikin
la source
2
Lorsque la minuterie s'arrête, vous affichez un message d'erreur, n'est-ce pas? Comment cela empêche-t-il votre pile TCP de se connecter? Imaginez un scénario où un hôte distant est à plus de 2 secondes, c'est-à-dire rto> 2. Votre minuterie s'arrêtera et vous imprimerez le message d'erreur. Cependant, TCP n'est pas contrôlé par votre minuterie. Il essaiera toujours de se connecter et pourra se connecter avec succès après 2 secondes. Est-ce que C # fournit une fonctionnalité pour annuler les demandes de "connexion" ou une fermeture sur socket. Votre solution de minuterie équivaut à vérifier après 2 secondes si la connexion a réussi.
Aditya Sehgal
J'ai trouvé ceci: splinter.com.au/blog/?p=28 On dirait que c'est le chemin. Il est similaire au vôtre mais je pense qu'il fait ce que j'ai expliqué ci-dessus.
Aditya Sehgal
Lorsqu'il expire, vous devez appeler m_clientSocket.Close ();
Vincent McNabb
Mise à jour, le lien de mon blog référencé par aditya a changé: splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
Chris
Je réécrirais la logique liée à l'appel "timer_connection.Dispose ();". La référence d'objet timer_connection est éventuellement utilisée après la suppression de l'objet.
BoiseBaked
2

Vérifiez ceci sur MSDN . Il ne semble pas que vous puissiez le faire avec les propriétés implémentées dans la classe Socket.

L'affiche sur MSDN a en fait résolu son problème en utilisant le filetage. Il avait un thread principal qui appelait d'autres threads qui exécutent le code de connexion pendant quelques secondes, puis vérifient la propriété Connected du socket:

J'ai créé une autre méthode qui connectait réellement le socket ... a mis le thread principal en veille pendant 2 secondes, puis vérifiez la méthode de connexion (qui est exécutée dans un thread séparé) si le socket était bien connecté, sinon lancez une exception "Timed out" et c'est tout. Merci encore pour les réponses.

Qu'essayez-vous de faire et pourquoi ne peut-il pas attendre 15 à 30 secondes avant de se terminer?

eric.christensen
la source
2

J'ai eu le même problème lors de la connexion à un socket et j'ai trouvé la solution ci-dessous, cela fonctionne très bien pour moi. »

private bool CheckConnectivityForProxyHost(string hostName, int port)
       {
           if (string.IsNullOrEmpty(hostName))
               return false;

           bool isUp = false;
           Socket testSocket = null;

           try
           {

               testSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
               IPAddress ip = null;
               if (testSocket != null && NetworkingCollaboratorBase.GetResolvedConnecionIPAddress(hostName, out ip))//Use a method to resolve your IP
               {
                   IPEndPoint ipEndPoint = new IPEndPoint(ip, port);

                   isUp = false;
//time out 5 Sec
                  CallWithTimeout(ConnectToProxyServers, 5000, testSocket, ipEndPoint);

                       if (testSocket != null && testSocket.Connected)
                       {
                           isUp = true;
                       }
                   }

               }
           }
           catch (Exception ex)
           {
               isUp = false;
           }
           finally
           {
               try
               {
                   if (testSocket != null)
                   {
                       testSocket.Shutdown(SocketShutdown.Both);
                   }
               }
               catch (Exception ex)
               {

               }
               finally
               {
                   if (testSocket != null)
                       testSocket.Close();
               }

           }

           return isUp;
       }


 private void CallWithTimeout(Action<Socket, IPEndPoint> action, int timeoutMilliseconds, Socket socket, IPEndPoint ipendPoint)
       {
           try
           {
               Action wrappedAction = () =>
               {
                   action(socket, ipendPoint);
               };

               IAsyncResult result = wrappedAction.BeginInvoke(null, null);

               if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
               {
                   wrappedAction.EndInvoke(result);
               }

           }
           catch (Exception ex)
           {

           }
       }

  private void ConnectToProxyServers(Socket testSocket, IPEndPoint ipEndPoint)
       {
           try
           {
               if (testSocket == null || ipEndPoint == null)
                   return;

                   testSocket.Connect(ipEndPoint);

           }
           catch (Exception ex)
           {

           }
       } 
Tyronne Thomas
la source
1

J'ai travaillé avec Unity et j'ai eu des problèmes avec BeginConnect et d'autres méthodes asynchrones de socket.

Il y a quelque chose que je ne comprends pas mais les exemples de code précédents ne fonctionnent pas pour moi.

J'ai donc écrit ce morceau de code pour le faire fonctionner. Je le teste sur un réseau adhoc avec android et pc, également en local sur mon ordinateur. J'espère que cela peut vous aider.

using System.Net.Sockets;
using System.Threading;
using System.Net;
using System;
using System.Diagnostics;

class ConnexionParameter : Guardian
{
    public TcpClient client;
    public string address;
    public int port;
    public Thread principale;
    public Thread thisthread = null;
    public int timeout;

    private EventWaitHandle wh = new AutoResetEvent(false);

    public ConnexionParameter(TcpClient client, string address, int port, int timeout, Thread principale)
    {
        this.client = client;
        this.address = address;
        this.port = port;
        this.principale = principale;
        this.timeout = timeout;
        thisthread = new Thread(Connect);
    }


    public void Connect()
    {
        WatchDog.Start(timeout, this);
        try
        {
            client.Connect(IPAddress.Parse(address), port);

        }
        catch (Exception)
        {
            UnityEngine.Debug.LogWarning("Unable to connect service (Training mode? Or not running?)");
        }
        OnTimeOver();
        //principale.Resume();
    }

    public bool IsConnected = true;
    public void OnTimeOver()
    {
        try
        {
            if (!client.Connected)
            {
                    /*there is the trick. The abort method from thread doesn't
 make the connection stop immediately(I think it's because it rise an exception
 that make time to stop). Instead I close the socket while it's trying to
 connect , that make the connection method return faster*/
                IsConnected = false;

                client.Close();
            }
            wh.Set();

        }
        catch(Exception)
        {
            UnityEngine.Debug.LogWarning("Connexion already closed, or forcing connexion thread to end. Ignore.");
        }
    }


    public void Start()
    {

        thisthread.Start();
        wh.WaitOne();
        //principale.Suspend();
    }

    public bool Get()
    {
        Start();
        return IsConnected;
    }
}


public static class Connexion
{


    public static bool Connect(this TcpClient client, string address, int port, int timeout)
    {
        ConnexionParameter cp = new ConnexionParameter(client, address, port, timeout, Thread.CurrentThread);
        return cp.Get();
    }

//http://stackoverflow.com/questions/19653588/timeout-at-acceptsocket
    public static Socket AcceptSocket(this TcpListener tcpListener, int timeoutms, int pollInterval = 10)
    {
        TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutms);
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        while (stopWatch.Elapsed < timeout)
        {
            if (tcpListener.Pending())
                return tcpListener.AcceptSocket();

            Thread.Sleep(pollInterval);
        }
        return null;
    }


}

et il y a un chien de garde très simple sur C # pour le faire fonctionner:

using System.Threading;

public interface Guardian
{
    void OnTimeOver();
}

public class WatchDog {

    int m_iMs;
    Guardian m_guardian;

    public WatchDog(int a_iMs, Guardian a_guardian)
    {
        m_iMs = a_iMs;
        m_guardian = a_guardian;
        Thread thread = new Thread(body);
        thread.Start(this);
    }


    private void body(object o)
    {
        WatchDog watchdog = (WatchDog)o;
        Thread.Sleep(watchdog.m_iMs);
        watchdog.m_guardian.OnTimeOver();
    }

    public static void Start(int a_iMs, Guardian a_guardian)
    {
        new WatchDog(a_iMs, a_guardian);
    }
}
Hugo Zevetel
la source
1

C'est comme la réponse de FlappySock, mais j'y ai ajouté un rappel parce que je n'aimais pas la mise en page et comment le booléen était renvoyé. Dans les commentaires de cette réponse de Nick Miller:

D'après mon expérience, si le point final peut être atteint, mais qu'il n'y a pas de serveur sur le point final capable de recevoir la connexion, alors AsyncWaitHandle.WaitOne sera signalé, mais la socket restera déconnectée

Donc, pour moi, il semble que se fier à ce qui est retourné peut être dangereux - je préfère l'utiliser socket.Connected. J'ai défini un booléen nullable et je le mets à jour dans la fonction de rappel. J'ai également trouvé qu'il ne finissait pas toujours de rapporter le résultat avant de revenir à la fonction principale - je gère cela aussi et je le fais attendre le résultat en utilisant le délai d'expiration:

private static bool? areWeConnected = null;

private static bool checkSocket(string svrAddress, int port)
{
    IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(svrAddress), port);
    Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

    int timeout = 5000; // int.Parse(ConfigurationManager.AppSettings["socketTimeout"].ToString());
    int ctr = 0;
    IAsyncResult ar = socket.BeginConnect(endPoint, Connect_Callback, socket);
    ar.AsyncWaitHandle.WaitOne( timeout, true );

    // Sometimes it returns here as null before it's done checking the connection
    // No idea why, since .WaitOne() should block that, but it does happen
    while (areWeConnected == null && ctr < timeout)
    {
        Thread.Sleep(100);
        ctr += 100;
    } // Given 100ms between checks, it allows 50 checks 
      // for a 5 second timeout before we give up and return false, below

    if (areWeConnected == true)
    {
        return true;
    }
    else
    {
        return false;
    }
}

private static void Connect_Callback(IAsyncResult ar)
{
    areWeConnected = null;
    try
    {
        Socket socket = (Socket)ar.AsyncState;
        areWeConnected = socket.Connected;
        socket.EndConnect(ar);
    }
    catch (Exception ex)
    {
      areWeConnected = false;
      // log exception 
    }
}

En relation: Comment vérifier si je suis connecté?

vapcguy
la source
-8

Il doit y avoir une propriété ReceiveTimeout dans la classe Socket.

Socket.ReceiveTimeout, propriété

Colin
la source
1
J'ai essayé. Cela ne fonctionne tout simplement pas. J'ai ajouté m_clientSocket.ReceiveTimeout = 1000; avant d'appeler m_clientSocket.Connect (ipEnd). Cependant, il attend toujours environ 15 à 20 secondes avant de faire apparaître le message d'exception.
ninikin
2
Ceci définit le délai d'expiration de la réception des données par le socket une fois la connexion établie.
eric.christensen
1
Ne peut pas utiliser ReceiveTimeout- ceci est strictement pour la réception avec BeginReceiveet EndReceive. Il n'y a pas d'équivalent lorsque vous voyez simplement si vous êtes connecté.
vapcguy