Как настроить таймаут подключения к сокету


105

Когда Клиент пытается подключиться к отключенному IP-адресу, возникает длительный тайм-аут более 15 секунд ... Как мы можем уменьшить этот таймаут? Как это можно настроить?

Код, который я использую для установки сокет-соединения, следующий:

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

Ответы:


147

Я нашел это. Проще, чем принятый ответ, и работает с .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
Хорошо, мало информации об этом - мне это нравится, и в нем намного меньше кода ... однако! Успех не является правильным условием. Вместо этого добавьте if (! _Socket.Connected), и это будет работать намного лучше. Я дам ему +1, потому что чем меньше, тем лучше.
TravisWhidden

2
Зависит от ваших двух конечных точек. Если они оба находятся в центре обработки данных, то 1 секунды должно быть достаточно, 3 для хорошей меры, 10 для доброты. Если один конец находится на мобильном устройстве, таком как смартфон, возможно, вы смотрите на 30 секунд.
FlappySocks

3
Другое дело слишком высматривать ... Если вместо того , чтобы nullв для callbackи вы планируете EndConnect(), если сокет был closedтогда это даст вам исключение. Так что не забудьте проверить ...
poy

9
Что если я хочу УВЕЛИЧИТЬ время ожидания, а не УМЕНЬШИТЬ его? Я думаю, что асинхронный подход просто позволяет вам заставить код не ждать 20 секунд (внутренний тайм-аут установлен в socket connect). Но в случае, если соединение займет больше времени, BeginConnect все равно остановится. Или BeginConnect внутренне ждет вечно? У меня очень медленное соединение, иногда для подключения требуется до 30-40 секунд, а таймауты на 21-й секунде возникают очень часто.
Alex

3
@TravisWhidden Могу подтвердить, это очень важно! По моему опыту, если конечная точка может быть достигнута, но на конечной точке нет сервера, способного получить соединение, то AsyncWaitHandle.WaitOneбудет сигнализироваться, но сокет останется неподключенным.
Николас Миллер

29

Мое мнение:

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

Я взял на себя смелость справиться с состоянием. Надеюсь, ты не против.
Hemant

Согласно комментариям к ответу с самым высоким рейтингом, который выглядит как копия, за исключением того, что он превращен в SocketExtension, вы все еще не использовали, .Connectedчтобы проверить, есть ли у вас, и вы не используете socket.Connected = true;для определения success.
vapcguy

22

Я просто написал класс расширения, чтобы разрешить тайм-ауты в соединениях. Используйте его точно так же, как и стандартные Connect()методы, с дополнительным параметром с именем 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
Поклонник GhostDoc, а? ;-) "Асинхронное соединение" - классический GhostDoc WTFness.
KeithS

1
:) да, а иногда даже не читатель того, что сгенерировано.
picrap

Лучше socket.EndConnect, чем socket.Close?
Kiquenet

3
Для socket.EndConnectзакрытия требуется ~ 10 секунд, поэтому функция возвращается не по прошествии заданного промежутка времени, а по прошествии промежутка времени + время
endConnect

8

Я не программирую на C #, но на C мы решаем ту же проблему, делая сокет неблокирующим, а затем помещая fd в цикл выбора / опроса со значением тайм-аута, равным времени, в течение которого мы готовы ждать подключения. преуспеть.

Я нашел это для Visual C ++, и объяснение там также склоняется к механизму выбора / опроса, который я объяснял ранее.

По моему опыту, вы не можете изменить значения таймаута подключения для каждого сокета. Вы меняете его для всех (настраивая параметры ОС).


7

может быть уже слишком поздно, но есть отличное решение на основе 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;
        }

Принимает ли какая-либо перегрузка ConnectAsync хост и порт?
marsh-wiggle

@ marsh-wiggle, метод ConnectAsync имеет 4 перегрузки docs.microsoft.com/en-us/dotnet/api/… Пожалуйста, посмотрите раздел «Методы расширения»
Олег Бондаренко,

1
@OlegBondarenko хорошо, недоступно для .net 4.5.1. Я сам должен его заворачивать. Спасибо!
marsh-wiggle

5

Я решил проблему, используя метод Socket.ConnectAsync вместо метода Socket.Connect. После вызова Socket.ConnectAsync (SocketAsyncEventArgs) запустите таймер (timer_connection), если время истекло, проверьте, подключено ли соединение с сокетом (если (m_clientSocket.Connected)), если нет, появится ошибка тайм-аута.

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
Когда таймер останавливается, вы показываете сообщение об ошибке, верно? Как это мешает вашему TCP-стеку фактически подключаться? Представьте себе сценарий, в котором удаленный хост находится на расстоянии более 2 секунд, т.е. rto> 2. Таймер остановится, и вы напечатаете сообщение об ошибке. Однако ваш таймер не контролирует TCP. Он по-прежнему будет пытаться подключиться и может успешно подключиться через 2 секунды. Предоставляет ли C # возможность отмены запросов на «подключение» или закрытия сокета. Ваше решение с таймером эквивалентно проверке через 2 секунды, успешно ли выполнено соединение.
Адитья Сегал,

Я нашел это: splinter.com.au/blog/?p=28 Похоже, это способ. Он похож на ваш, но я думаю, что он делает то, что я объяснил выше.
Адитья Сегал,

По истечении времени ожидания следует вызвать m_clientSocket.Close ();
Винсент Макнабб

Обновление, ссылка в моем блоге, на которую ссылается Адитья, изменилась: splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
Крис

Я бы переписал логику, связанную с вызовом «timer_connection.Dispose ();». Ссылка на объект timer_connection может использоваться после удаления объекта.
BoiseBaked

2

Проверьте это на MSDN . Не похоже, что вы можете сделать это с помощью реализованных свойств в классе Socket.

Плакат на MSDN фактически решил свою проблему с помощью потоковой передачи. У него был основной поток, который вызывал другие потоки, которые запускали код соединения в течение пары секунд, а затем проверяли свойство Connected сокета:

Я создал другой метод, который фактически подключил сокет ... у основного потока был спящий режим в течение 2 секунд, а затем проверьте метод подключения (который выполняется в отдельном потоке), если сокет был подключен правильно, в противном случае выдается исключение «Время ожидания истекло» и это все. Еще раз спасибо за пополнение.

Что вы пытаетесь сделать, и почему он не может подождать 15-30 секунд до истечения времени ожидания?


2

У меня была такая же проблема при подключении к сокету, и я придумал следующее решение: оно отлично работает для меня. `

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

Я работал с Unity, и у меня были проблемы с BeginConnect и другими асинхронными методами из сокета.

Я кое-что не понимаю, но предыдущие примеры кода мне не подходят.

Итак, я написал этот фрагмент кода, чтобы он работал. Я тестирую его в специальной сети с Android и ПК, а также локально на моем компьютере. Надеюсь, это поможет.

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


}

и есть очень простой сторожевой таймер на C #, чтобы заставить его работать:

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

Это похоже на ответ FlappySock, но я добавил к нему обратный вызов, потому что мне не понравился макет и то, как возвращалось логическое значение. В комментариях к этому ответу Ника Миллера:

По моему опыту, если конечная точка может быть достигнута, но на конечной точке нет сервера, способного получить соединение, то будет сигнализироваться AsyncWaitHandle.WaitOne, но сокет останется неподключенным.

Поэтому мне кажется, что полагаться на то, что возвращается, может быть опасно - я предпочитаю использовать socket.Connected. Я устанавливаю логическое значение, допускающее значение NULL, и обновляю его в функции обратного вызова. Я также обнаружил, что он не всегда завершает отчет о результате, прежде чем вернуться к основной функции - я тоже обрабатываю это и заставляю его ждать результата, используя тайм-аут:

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

По теме: Как проверить, подключен ли я?


-8

В классе Socket должно быть свойство ReceiveTimeout.

Socket.ReceiveTimeout Свойство


1
Я попытался. Это просто не работает. Я добавил m_clientSocket.ReceiveTimeout = 1000; перед вызовом m_clientSocket.Connect (ipEnd). Однако он все еще ждет около 15-20 секунд, прежде чем появится сообщение об исключении.
ninikin

2
Это устанавливает тайм-аут, когда сокет получает данные после того, как соединение было установлено.
eric.christensen

1
Невозможно использовать ReceiveTimeout- это строго при получении с помощью BeginReceiveи EndReceive. Нет эквивалента, когда вы просто видите, подключены ли вы.
vapcguy
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.