Як налаштувати тайм-аут підключення до сокета


104

Коли Клієнт намагається підключитися до відключеної 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);
}

Відповіді:


146

Я це знайшов. Простіше, ніж прийнята відповідь, і працює з .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, тим менше - це більше аспект.
TravisВідкритий

2
Залежить від ваших двох кінцевих точок. Якщо вони обоє в центрі обробки даних, то 1 секунди повинно бути багато, 3 - на добру міру, 10 - щоб бути добрими. Якщо один кінець стоїть на мобільному пристрої, наприклад смартфоні, ви можете дивитися 30 секунд.
FlappySocks

3
Інша річ теж слідкуйте за тим, що якщо замість того, щоб подати заявку nullна callbackі ви плануєте EndConnect(), якщо сокет був, closedто це дасть вам виняток. Тож переконайтесь, що ви перевірили ...
пой

9
Що робити, якщо я хочу збільшити час очікування, а не зменшити його? Я думаю, що підхід асинхронізації просто дозволяє змусити код не чекати 20 секунд (внутрішній тайм-аут, встановлений у сокеті, підключиться). Але у випадку, якщо підключення триватиме більше часу, BeginConnect все одно припиниться. Або чи вічно чекає BeginConnect? У мене дуже повільний зв'язок, коли для підключення іноді потрібно до 30-40 секунд, а тайм-аути на 21-й секунді трапляються дуже часто.
Олексій

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

Я взяв на себе сміливість лікувати стан. Сподіваюся, ви не заперечуєте.
Хемант

За коментарями до найвищої оцінки, яка, схоже, є копією, за винятком того, що вона зроблена в "Ви" 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, так? ;-) "Asyncs the connect" - класичний GhostDoc WTFness.
KeithS

1
:) так, а іноді навіть не читач того, що генерується.
пікап

Краще socket.EndConnect ніж socket.Close?
Кікенет

3
Для socket.EndConnectзакриття потрібно ~ 10 секунд, щоб функція поверталася не через проміжок часу, а через проміжок часу + кінецьЗаключити час
Royi Namir

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, метод "ConnectAsync" має 4 перевантаження docs.microsoft.com/en-us/dotnet/api/… Перегляньте розділ Методи розширення
Олег Бондаренко

1
@OlegBondarenko ок, не доступний для .net 4.5.1. Я сам мушу це обгортати. Дякую!
болото-хитання

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 секунди перевірити, чи вдалося з'єднання.
Aditya Sehgal

Я знайшов це: splinter.com.au/blog/?p=28 Схоже, це такий шлях. Він схожий на ваш, але я думаю, він робить те, що я пояснив вище.
Aditya Sehgal

Коли час закінчується, вам слід зателефонувати m_clientSocket.Close ();
Вінсент Макнабб

Оновлення, посилання на мій блог, на яке посилається aditya, змінилося: 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

У мене була така ж проблема під час підключення до Socket, і я придумав рішення, яке наведено нижче, для мене це прекрасно. `

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 та іншими методами асинхронізації з сокета.

Є щось, що я не розумію, але зразки коду раніше не працювали для мене.

Тому я написав цей фрагмент коду, щоб він працював. Я тестую його в мережі adhoc з android та pc, також локально на своєму комп’ютері. Сподіваюся, це може допомогти.

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. Я встановлюю нульовий Boolean і оновлюю його у функції зворотного дзвінка. Я також виявив, що він не завжди закінчує звітувати про результат перед поверненням до основної функції - я також впораюся з цим і змушую його чекати результат, використовуючи тайм-аут:

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 секунд, перш ніж з'явиться повідомлення про виключення.
нінікін

2
Це встановлює час очікування, коли сокет приймає дані після встановлення з'єднання.
eric.christensen

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