Como configurar o tempo limite de conexão do soquete

104

Quando o Cliente tenta se conectar a um endereço IP desconectado, ocorre um longo tempo limite de mais de 15 segundos ... Como podemos reduzir esse tempo limite? Qual é o método para configurá-lo?

O código que estou usando para configurar uma conexão de soquete é o seguinte:

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
fonte

Respostas:

146

Eu achei isto. Mais simples do que a resposta aceita e funciona com .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
fonte
20
OK, pouca entrada sobre este - eu gosto disso e é muito menos código .. no entanto, o sucesso não é a condição correta. Em vez disso, adicione if (! _Socket.Connected) e funcionará muito melhor. Eu darei um +1 para o menos é mais aspecto.
TravisWhidden
2
Depende de seus dois pontos finais. Se ambos estiverem em um datacenter, então 1 segundo deve ser suficiente, 3 para uma boa medida, 10 para ser gentil. Se uma extremidade estiver em um dispositivo móvel, como um smartphone, você poderá estar olhando para 30 segundos.
FlappySocks
3
Outra coisa também deve estar atenta ... Se ao invés de colocar nullno callbacke você planeja EndConnect(), se o socket foi, closedentão isso vai te dar uma exceção. Portanto, certifique-se de verificar ...
poy
9
E se eu quiser AUMENTAR o tempo limite em vez de DIMINUI-lo? Acho que a abordagem assíncrona apenas permite que você faça com que o código não espere 20 segundos (o tempo limite interno definido na conexão de soquete). Mas caso a conexão demore mais, o BeginConnect irá parar de qualquer maneira. Ou o BeginConnect internamente espera para sempre? Tenho uma conexão muito lenta, quando às vezes é necessário até 30-40 segundos para se conectar e tempos limite de 21 segundos ocorrem com muita frequência.
Alex
3
@TravisWhidden Pode confirmar, isso é muito importante! Na minha experiência, se o ponto de extremidade puder ser alcançado, mas não houver nenhum servidor no ponto de extremidade capaz de receber a conexão, então AsyncWaitHandle.WaitOneserá sinalizado, mas o soquete permanecerá desconectado.
Nicholas Miller
29

Minha vez:

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
fonte
Tomei a liberdade de lidar com uma condição. Espero que você não se importe.
Hemant
Por comentários sobre a resposta com melhor classificação, da qual esta parece ser uma cópia, exceto que é transformada em um SocketExtension, você ainda não usou .Connectedpara ver se está e não está usando socket.Connected = true;para definir success.
vapcguy
22

Acabei de escrever uma classe de extensão para permitir tempos limite nas conexões. Use-o exatamente como faria com os Connect()métodos padrão , com um parâmetro extra chamado 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
fonte
8
Fã de GhostDoc, não é? ;-) "Asyncs the connect" - clássico GhostDoc WTFness.
KeithS
1
:) sim, e às vezes nem mesmo um leitor do que foi gerado.
picrap
Melhor socket.EndConnect do que socket.Close?
Kiquenet
3
O socket.EndConnectleva cerca de 10 segundos para fechar, então a função retorna não após o intervalo de tempo, mas após o intervalo de tempo + tempo final de conexão
Royi Namir
8

Eu não programo em C #, mas em C, resolvemos o mesmo problema tornando o soquete não bloqueante e, em seguida, colocando o fd em um loop de seleção / pesquisa com um valor de tempo limite igual ao tempo que estamos dispostos a esperar pela conexão ter sucesso.

Achei isso para Visual C ++ e a explicação lá também se inclina para o mecanismo de seleção / votação que expliquei antes.

Na minha experiência, você não pode alterar os valores de tempo limite de conexão por soquete. Você muda para todos (ajustando os parâmetros do sistema operacional).

Aditya Sehgal
fonte
7

pode ser tarde demais, mas há uma solução interessante baseada em 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
fonte
Alguma sobrecarga de "ConnectAsync" aceita host e porta?
Marsh-wiggle
@ marsh-wiggle, o método "ConnectAsync" tem 4 sobrecargas. docs.microsoft.com/en-us/dotnet/api/… Consulte a seção Métodos de extensão
Oleg Bondarenko
1
@OlegBondarenko ok, não disponível para .net 4.5.1. Eu mesmo tenho que embrulhar. Obrigado!
marsh-wiggle
5

Resolvi o problema usando o método Socket.ConnectAsync em vez do método Socket.Connect. Após invocar o Socket.ConnectAsync (SocketAsyncEventArgs), inicie um cronômetro (timer_connection), se o tempo acabou, verifique se a conexão do soquete está conectada (if (m_clientSocket.Connected)), se não, pop up timeout error.

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
fonte
2
Quando o cronômetro para, você mostra uma mensagem de erro, certo? Como isso impede que sua pilha TCP realmente se conecte. Imagine um cenário em que um host remoto está a mais de 2 segundos de distância, ou seja, rto> 2. Seu cronômetro irá parar e você imprimirá a mensagem de erro. No entanto, o TCP não é controlado pelo cronômetro. Ele ainda tentará se conectar e poderá se conectar com êxito após 2 segundos. C # fornece um recurso para cancelar solicitações de "conexão" ou um fechamento no soquete. Sua solução de temporizador é igual a verificar após 2 segundos se a conexão foi bem-sucedida.
Aditya Sehgal
Eu encontrei este: splinter.com.au/blog/?p=28 Parece que este é o caminho. É semelhante ao seu, mas acho que corresponde ao que expliquei acima.
Aditya Sehgal
Quando atingir o tempo limite, você deve chamar m_clientSocket.Close ();
Vincent McNabb
Atualização, o link do meu blog conforme referido por aditya mudou: splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
Chris
Eu reescreveria a lógica relacionada à chamada "timer_connection.Dispose ();". A referência do objeto timer_connection é possivelmente usada após o objeto ser descartado.
BoiseBaked em
2

Verifique isso no MSDN . Não parece que você pode fazer isso com as propriedades implementadas na classe Socket.

O pôster no MSDN realmente resolveu seu problema usando threading. Ele tinha um encadeamento principal que chamava outros encadeamentos que executam o código de conexão por alguns segundos e, em seguida, verificam a propriedade Connected do soquete:

Eu criei outro método que realmente conectou o soquete ... deixou o thread principal hibernando por 2 segundos e então verifiquei no método de conexão (que é executado em um thread separado) se o soquete estava bem conectado, caso contrário, lançar uma exceção "Tempo esgotado" e isso é tudo. Obrigado novamente pelas respostas.

O que você está tentando fazer e por que não pode esperar 15-30 segundos antes de expirar?

eric.christensen
fonte
2

Eu tive o mesmo problema ao conectar a um soquete e eu vim com a solução abaixo, funciona bem para mim. `

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

Trabalhei com o Unity e tive alguns problemas com o BeginConnect e outros métodos assíncronos do socket.

Há algo que eu não entendo, mas os exemplos de código anteriores não funcionam para mim.

Então, escrevi este código para fazê-lo funcionar. Eu testei em uma rede adhoc com android e pc, também em local no meu computador. Espero que possa ajudar.

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 há um watchdog muito simples em C # para fazê-lo funcionar:

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

É como a resposta do FlappySock, mas adicionei um retorno de chamada porque não gostei do layout e de como o booleano estava sendo retornado. Nos comentários dessa resposta de Nick Miller:

Na minha experiência, se o ponto de extremidade puder ser alcançado, mas não houver nenhum servidor no ponto de extremidade capaz de receber a conexão, então AsyncWaitHandle.WaitOne será sinalizado, mas o soquete permanecerá desconectado

Então, para mim, parece que confiar no que é devolvido pode ser perigoso - prefiro usar socket.Connected. Defino um Booleano anulável e atualizo-o na função de retorno de chamada. Também descobri que nem sempre termina de relatar o resultado antes de retornar à função principal - eu também cuido disso e faço esperar pelo resultado usando o tempo limite:

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: Como verificar se estou conectado?

vapcguy
fonte
-8

Deve haver uma propriedade ReceiveTimeout na classe Socket.

Propriedade Socket.ReceiveTimeout

Colin
fonte
1
Eu tentei. Simplesmente não funciona. Eu adicionei m_clientSocket.ReceiveTimeout = 1000; antes de invocar m_clientSocket.Connect (ipEnd). No entanto, ele ainda espera cerca de 15-20 segundos antes de exibir a mensagem de exceção.
ninikin
2
Isso define o tempo limite para quando o soquete está recebendo dados após a conexão ter sido feita.
eric.christensen
1
Não é possível usar ReceiveTimeout- destina-se estritamente ao recebimento com BeginReceivee EndReceive. Não há equivalente para quando você apenas vê se está conectado.
vapcguy