Cómo configurar el tiempo de espera de conexión de socket

104

Cuando el Cliente intenta conectarse a una dirección IP desconectada, hay un tiempo de espera de más de 15 segundos ... ¿Cómo podemos reducir este tiempo de espera? ¿Cuál es el método para configurarlo?

El código que estoy usando para configurar una conexión de socket es el siguiente:

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
fuente

Respuestas:

146

Encontré esto. Más simple que la respuesta aceptada y funciona 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.");
}

//... 
FlappySocks
fuente
20
De acuerdo, poca información sobre esto, me gusta esto y es mucho menos código ... sin embargo, el éxito no es la condición correcta. En su lugar, agregue if (! _Socket.Connected) y funciona mucho mejor. Le daré un +1 por cuanto menos es más aspecto.
TravisWhidden
2
Depende de sus dos puntos finales. Si ambos están en un centro de datos, entonces 1 segundo debería ser suficiente, 3 en buena medida, 10 para ser amable. Si un extremo está en un dispositivo móvil, como un teléfono inteligente, es posible que esté mirando 30 segundos.
FlappySocks
3
También hay que tener en cuenta otra cosa ... Si en lugar de colocar nullel callbacky lo planeas EndConnect(), si el socket ha sido closedasí, esto te dará una excepción. Así que asegúrese de verificar ...
poy
9
¿Qué pasa si quiero AUMENTAR el tiempo de espera en lugar de DISMINUIRLO? Creo que el enfoque asíncrono solo le permite hacer que el código no espere 20 segundos (el tiempo de espera interno establecido en la conexión de socket). Pero en caso de que la conexión demore más, BeginConnect se detendrá de todos modos. ¿O BeginConnect espera internamente para siempre? Tengo una conexión muy lenta cuando a veces se requiere hasta 30-40 segundos para conectar, y los tiempos de espera en el segundo 21 ocurren con mucha frecuencia.
Alex
3
@TravisWhidden Puede confirmar, ¡esto es muy importante! En mi experiencia, si se puede alcanzar el punto final, pero no hay ningún servidor en el punto final capaz de recibir la conexión, entonces se AsyncWaitHandle.WaitOneseñalará, pero el socket permanecerá desconectado.
Nicholas Miller
29

Mi toma:

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
fuente
Me tomé la libertad de manejar una condición. Espero que no te importe.
Hemant
Según los comentarios sobre la respuesta mejor calificada, de la que parece ser una copia, excepto que se convierte en una SocketExtension , todavía no ha usado .Connectedpara ver si lo está y no está usando socket.Connected = true;para definir success.
vapcguy
22

Acabo de escribir una clase de extensión para permitir tiempos de espera en las conexiones. Úselo exactamente como usaría los Connect()métodos estándar , con un parámetro adicional llamado 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
fuente
8
Fan de GhostDoc, ¿verdad? ;-) "Asyncs the connect" - clásico GhostDoc WTFness.
KeithS
1
:) sí, y en ocasiones ni siquiera un lector de lo que se ha generado.
picrap
Mejor socket.EndConnect que socket.Close?
Kiquenet
3
La socket.EndConnecttoma ~ 10 segundos para cerrar así la función devuelve no después del lapso de tiempo, pero después de intervalo de tiempo + endConnect tiempo
Royi Namir
8

No programo en C # pero en C, resolvemos el mismo problema haciendo que el socket no se bloquee y luego colocando el fd en un ciclo de selección / encuesta con un valor de tiempo de espera igual a la cantidad de tiempo que estamos dispuestos a esperar para la conexión para triunfar.

Encontré esto para Visual C ++ y la explicación también se inclina hacia el mecanismo de selección / encuesta que expliqué antes.

En mi experiencia, no puede cambiar los valores de tiempo de espera de conexión por socket. Lo cambia para todos (ajustando los parámetros del sistema operativo).

Aditya Sehgal
fuente
7

puede que sea demasiado tarde, pero hay una solución ordenada basada en 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
fuente
¿Alguna sobrecarga de "ConnectAsync" acepta host y puerto?
marsh-wiggle
@ marsh-wiggle, el método "ConnectAsync" tiene 4 sobrecargas docs.microsoft.com/en-us/dotnet/api/… Consulte la sección Métodos de extensión
Oleg Bondarenko
1
@OlegBondarenko ok, no disponible para .net 4.5.1. Tengo que envolverlo yo mismo. ¡Gracias!
marsh-wiggle
5

Resolví el problema usando el método Socket.ConnectAsync en lugar del método Socket.Connect. Después de invocar el Socket.ConnectAsync (SocketAsyncEventArgs), inicie un temporizador (timer_connection), si se acabó el tiempo, verifique si la conexión del socket está conectada (si (m_clientSocket.Connected)), si no, aparece el error de tiempo de espera.

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
fuente
2
Cuando el temporizador se detiene, muestra un mensaje de error, ¿verdad? ¿Cómo evita esto que su pila TCP se conecte realmente? Imagine una situación en la que un host remoto está a más de 2 segundos de distancia, es decir, rto> 2. Su temporizador se detendrá y se imprimirá el mensaje de error. Sin embargo, TCP no está controlado por su temporizador. Seguirá intentando conectarse y es posible que se conecte correctamente después de 2 segundos. ¿C # proporciona una función para cancelar solicitudes de "conexión" o un cierre de socket? Su solución de temporizador es igual a verificar después de 2 segundos si la conexión se realizó correctamente.
Aditya Sehgal
Encontré esto: splinter.com.au/blog/?p=28 Parece que este es el camino. Es similar al tuyo pero creo que hace lo que expliqué anteriormente.
Aditya Sehgal
Cuando se agota el tiempo de espera, debe llamar a m_clientSocket.Close ();
Vincent McNabb
Actualización, el enlace de mi blog al que hace referencia aditya ha cambiado: splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
Chris
Volvería a escribir la lógica relacionada con la llamada "timer_connection.Dispose ();". La referencia del objeto timer_connection posiblemente se utilice después de que se elimine el objeto.
BoiseBaked
2

Mire esto en MSDN . No parece que pueda hacer esto con las propiedades implementadas en la clase Socket.

El póster de MSDN realmente resolvió su problema usando subprocesos. Tenía un hilo principal que llamaba a otros hilos que ejecutan el código de conexión durante un par de segundos y luego verifican la propiedad Conectada del socket:

Creé otro método que realmente conectó el socket ... tenía el hilo principal en reposo durante 2 segundos y luego verifiqué el método de conexión (que se ejecuta en un hilo separado) si el socket estaba bien conectado; de lo contrario, lanza una excepción "Timed out" y eso es todo. Gracias de nuevo por las repletas.

¿Qué está tratando de hacer y por qué no puede esperar entre 15 y 30 segundos antes de que se agote el tiempo?

eric.christensen
fuente
2

Tuve el mismo problema al conectarme a un Socket y se me ocurrió la siguiente solución: funciona bien para mí. '

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
fuente
1

Trabajé con Unity y tuve algún problema con BeginConnect y otros métodos asincrónicos de socket.

Hay algo que no entiendo, pero los ejemplos de código anteriores no me funcionan.

Así que escribí este código para que funcione. Lo pruebo en una red adhoc con android y pc, también en local en mi computadora. Espero que pueda ayudar.

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


}

y hay un perro guardián muy simple en C # para que funcione:

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
fuente
1

Esta es como la respuesta de FlappySock, pero le agregué una devolución de llamada porque no me gustó el diseño y cómo se devolvió el booleano. En los comentarios de esa respuesta de Nick Miller:

En mi experiencia, si se puede alcanzar el punto final, pero no hay ningún servidor en el punto final capaz de recibir la conexión, entonces se señalará AsyncWaitHandle.WaitOne, pero el socket permanecerá desconectado

Entonces, para mí, parece que confiar en lo que se devuelve puede ser peligroso; prefiero usar socket.Connected. Configuré un booleano que acepta valores NULL y lo actualizo en la función de devolución de llamada. También descubrí que no siempre termina de informar el resultado antes de regresar a la función principal; también me ocupo de eso y lo hago esperar el resultado usando el tiempo de espera:

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

Relacionado: ¿Cómo comprobar si estoy conectado?

vapcguy
fuente
-8

Debería haber una propiedad ReceiveTimeout en la clase Socket.

Propiedad Socket.ReceiveTimeout

Colin
fuente
1
Lo intenté. Simplemente no funciona. Agregué m_clientSocket.ReceiveTimeout = 1000; antes invocó el m_clientSocket.Connect (ipEnd). Sin embargo, todavía espera entre 15 y 20 segundos antes de que aparezca el mensaje de excepción.
ninikin
2
Esto establece el tiempo de espera para cuando el socket recibe datos después de que se haya realizado la conexión.
eric.christensen
1
No se puede usar ReceiveTimeout: esto es estrictamente para cuando se recibe con BeginReceivey EndReceive. No hay equivalente para cuando solo está viendo si está conectado.
vapcguy