Come configurare il timeout di connessione del socket


104

Quando il client tenta di connettersi a un indirizzo IP disconnesso, c'è un lungo timeout di oltre 15 secondi ... Come possiamo ridurre questo timeout? Qual è il metodo per configurarlo?

Il codice che sto usando per impostare una connessione socket è il seguente:

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);
}

Risposte:


146

Ho trovato questo. Più semplice della risposta accettata e funziona con .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.");
}

//... 

20
OK, poco input su questo - mi piace ed è molto meno codice .. tuttavia il! Successo non è la condizione corretta. Invece, aggiungi if (! _Socket.Connected) e funziona molto meglio. Gli darò un +1 per il meno è più aspetto.
TravisWhidden

2
Dipende dai tuoi due punti finali. Se si trovano entrambi in un data center, 1 secondo dovrebbe essere sufficiente, 3 per buona misura, 10 per essere gentili. Se un'estremità si trova su un dispositivo mobile, come uno smartphone, potresti guardare 30 secondi.
FlappySocks

3
Un'altra cosa nullper cui callbackfai attenzione ... Se invece di inserire per e prevedi di EndConnect(), se il socket è stato, closedquesto ti darà un'eccezione. Quindi assicurati di controllare ...
Poy

9
E se volessi AUMENTARE il timeout invece di RIDURRE? Penso che l'approccio asincrono ti consenta di fare in modo che il codice non attenda per 20 secondi (il timeout interno impostato nella connessione socket). Ma nel caso in cui la connessione richieda più tempo, BeginConnect si fermerà comunque. Oppure BeginConnect attende internamente per sempre? Ho una connessione molto lenta quando a volte sono necessari fino a 30-40 secondi per connettermi e i timeout al 21 ° secondo si verificano molto spesso.
Alex

3
@TravisWhidden Posso confermare, questo è molto importante! Nella mia esperienza, se l'endpoint può essere raggiunto, ma non c'è nessun server sull'endpoint in grado di ricevere la connessione, allora AsyncWaitHandle.WaitOneverrà segnalato, ma il socket rimarrà scollegato.
Nicholas Miller

29

La mia opinione:

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.
        }
    }
}

Mi sono preso la libertà di gestire una condizione. Spero non ti dispiaccia.
Hemant

Per i commenti sulla risposta più votata, di cui questa sembra essere una copia tranne che è trasformata in un SocketExtension, non hai ancora .Connectedvisto se lo sei e non stai usando socket.Connected = true;per definire success.
vapcguy

22

Ho appena scritto una classe di estensione per consentire timeout nelle connessioni. Usalo esattamente come useresti i Connect()metodi standard , con un parametro aggiuntivo chiamato 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)
            { }
        }
    }

8
Fan di GhostDoc, vero? ;-) "Asyncs the connect" - classico GhostDoc WTFness.
KeithS

1
:) sì, ea volte nemmeno un lettore di ciò che è stato generato.
picrap

Meglio socket.EndConnect che socket.Close?
Kiquenet

3
La socket.EndConnectchiusura richiede ~ 10 secondi, quindi la funzione ritorna non dopo l'intervallo di tempo ma dopo l'intervallo di tempo + l'ora di fine connessione
Royi Namir

8

Non programma in C # ma in C, risolviamo lo stesso problema rendendo il socket non bloccante e quindi inserendo fd in un ciclo di selezione / polling con un valore di timeout uguale al tempo che siamo disposti ad aspettare per la connessione per avere successo.

Ho trovato questo per Visual C ++ e la spiegazione non si piega anche verso il meccanismo / sondaggio Scegli ho spiegato prima.

Nella mia esperienza, non è possibile modificare i valori di timeout di connessione per socket. Lo cambi per tutti (ottimizzando i parametri del sistema operativo).


7

potrebbe essere troppo tardi ma esiste una soluzione pulita basata su 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;
        }

Qualche sovraccarico "ConnectAsync" accetta host e porta?
marsh-wiggle

@ marsh-wiggle, il metodo "ConnectAsync" ha 4 overload docs.microsoft.com/en-us/dotnet/api/… Si prega di consultare la sezione Metodi di estensione
Oleg Bondarenko

1
@OlegBondarenko ok, non disponibile per .net 4.5.1. Devo avvolgerlo da solo. Grazie!
marsh-wiggle

5

Ho risolto il problema utilizzando il metodo Socket.ConnectAsync invece del metodo Socket.Connect. Dopo aver richiamato Socket.ConnectAsync (SocketAsyncEventArgs), avvia un timer (timer_connection), se il tempo è scaduto, controlla se la connessione socket è connessa (if (m_clientSocket.Connected)), in caso contrario, compare l'errore di timeout.

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();
        }
    }

2
Quando il timer si ferma, viene visualizzato un messaggio di errore, giusto? In che modo questo impedisce al tuo stack TCP di connettersi effettivamente. Immagina uno scenario in cui un host remoto è a più di 2 secondi di distanza, cioè rto> 2. Il timer si fermerà e stamperai il messaggio di errore. Tuttavia, il protocollo TCP non è controllato dal timer. Proverà comunque a connettersi e potrebbe connettersi correttamente dopo 2 secondi. C # fornisce una funzionalità per annullare le richieste di "connessione" o una chiusura sul socket? La soluzione del timer equivale a controllare dopo 2 secondi se la connessione è riuscita.
Aditya Sehgal,

Ho trovato questo: splinter.com.au/blog/?p=28 Sembra che questo sia il modo. È simile al tuo ma penso che faccia quello che ho spiegato sopra.
Aditya Sehgal

Quando scade, dovresti chiamare m_clientSocket.Close ();
Vincent McNabb

Aggiornamento, il link al mio blog a cui fa riferimento aditya è cambiato: splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
Chris

Riscriverei la logica relativa alla chiamata "timer_connection.Dispose ();". Il riferimento all'oggetto timer_connection può essere utilizzato dopo che l'oggetto è stato eliminato.
BoiseBaked

2

Controlla questo su MSDN . Non sembra che sia possibile farlo con le proprietà implementate nella classe Socket.

Il poster su MSDN ha effettivamente risolto il suo problema utilizzando il threading. Aveva un thread principale che chiamava altri thread che eseguono il codice di connessione per un paio di secondi e quindi controlla la proprietà Connected del socket:

Ho creato un altro metodo che ha effettivamente collegato il socket ... ha fatto dormire il thread principale per 2 secondi e poi ha controllato il metodo di connessione (che viene eseguito in un thread separato) se il socket era collegato bene altrimenti lancia un'eccezione "Timeout" e questo è tutto. Grazie ancora per i repleies.

Cosa stai cercando di fare e perché non puoi aspettare 15-30 secondi prima di scadere?


2

Ho avuto lo stesso problema durante la connessione a una presa e ho trovato la soluzione seguente, funziona bene per me. `

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)
           {

           }
       } 

1

Ho lavorato con Unity e ho avuto qualche problema con BeginConnect e altri metodi asincroni dal socket.

C'è qualcosa che non capisco, ma gli esempi di codice precedenti non funzionano per me.

Quindi ho scritto questo pezzo di codice per farlo funzionare. Lo provo su una rete ad hoc con android e pc, anche in locale sul mio computer. Spero che possa aiutare.

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;
    }


}

e c'è un watchdog molto semplice su C # per farlo funzionare:

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);
    }
}

1

Questa è come la risposta di FlappySock, ma ho aggiunto una richiamata perché non mi piaceva il layout e il modo in cui veniva restituito il booleano. Nei commenti di quella risposta di Nick Miller:

Nella mia esperienza, se l'endpoint può essere raggiunto, ma non c'è nessun server sull'endpoint in grado di ricevere la connessione, allora AsyncWaitHandle.WaitOne verrà segnalato, ma il socket rimarrà scollegato

Quindi a me sembra che fare affidamento su ciò che viene restituito possa essere pericoloso - preferisco usare socket.Connected. Ho impostato un valore booleano nullable e lo aggiorno nella funzione di callback. Ho anche scoperto che non finisce sempre di riportare il risultato prima di tornare alla funzione principale - lo gestisco anche io e faccio aspettare il risultato usando il timeout:

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 
    }
}

Correlati: come verificare se sono connesso?


-8

Dovrebbe essere presente una proprietà ReceiveTimeout nella classe Socket.

Proprietà Socket.ReceiveTimeout


1
Provai. Semplicemente non funziona. Ho aggiunto m_clientSocket.ReceiveTimeout = 1000; prima di chiamare m_clientSocket.Connect (ipEnd). Tuttavia, attende ancora circa 15-20 secondi prima di visualizzare il messaggio di eccezione.
ninikin

2
Questo imposta il timeout per quando il socket riceve dati dopo che è stata stabilita la connessione.
eric.christensen

1
Non posso usare ReceiveTimeout- questo è strettamente per quando si riceve con BeginReceivee EndReceive. Non c'è equivalente per quando stai solo vedendo se sei connesso.
vapcguy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.