Jak skonfigurować limit czasu połączenia gniazda


104

Gdy klient próbuje połączyć się z rozłączonym adresem IP, upływa długi czas ponad 15 sekund ... Jak możemy skrócić ten limit czasu? Jaka jest metoda konfiguracji?

Kod, którego używam do konfigurowania połączenia z gniazdem, jest następujący:

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

Odpowiedzi:


146

Znalazłem to. Prostsza niż zaakceptowana odpowiedź i działa z .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, mało danych wejściowych na ten temat - podoba mi się ten i jest dużo mniej kodu ... jednak sukces! Nie jest prawidłowym warunkiem. Zamiast tego dodaj if (! _Socket.Connected) i działa znacznie lepiej. Dam mu +1, bo mniej znaczy więcej.
TravisWhidden

2
Zależy od twoich dwóch punktów końcowych. Jeśli oboje znajdują się w centrum danych, 1 sekunda powinna wystarczyć, 3 na miarę, 10 na uprzejmość. Jeśli jeden koniec znajduje się na urządzeniu mobilnym, takim jak smartfon, możesz patrzeć na 30 sekund.
FlappySocks

3
Inną rzeczą zbyt wypatrywać ... Jeśli zamiast umieszczać nullw dla callbacki planujesz EndConnect(), jeśli gniazdo jest closedwtedy to daje wyjątek. Więc upewnij się, że sprawdziłeś ...
poy

9
A co jeśli chcę ZWIĘKSZYĆ limit czasu zamiast go ZMNIEJSZYĆ? Myślę, że podejście asynchroniczne po prostu pozwala sprawić, by kod nie czekał przez 20 sekund (wewnętrzny limit czasu ustawiony w połączeniu gniazda). Ale w przypadku, gdy połączenie trwa dłużej, BeginConnect i tak się zatrzyma. A może BeginConnect wewnętrznie czeka wiecznie? Mam bardzo powolne połączenie, gdy czasami potrzeba do 30-40 sekund na nawiązanie połączenia, a przekroczenia czasu oczekiwania na 21. sekundę występują bardzo często.
Alex

3
@TravisWhidden Can potwierdzić, to jest bardzo ważne! Z mojego doświadczenia wynika, że ​​jeśli punkt końcowy można osiągnąć, ale na punkcie końcowym nie ma serwera, który mógłby odebrać połączenie, AsyncWaitHandle.WaitOnezostanie to zasygnalizowane, ale gniazdo pozostanie niepodłączone.
Nicholas Miller,

29

Moje podanie:

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

Pozwoliłem sobie poradzić sobie z chorobą. Mam nadzieję, że nie masz nic przeciwko.
Hemant

Zgodnie z komentarzami do najwyżej ocenianej odpowiedzi, która wygląda na kopię, z wyjątkiem tego, że została przekształcona w a SocketExtension, nadal nie .Connectedsprawdzałeś, czy tak jest i nie używasz socket.Connected = true;do definiowania success.
vapcguy

22

Właśnie napisałem klasę rozszerzenia, aby umożliwić przekroczenie limitu czasu połączeń. Użyj go dokładnie tak, jak używałbyś standardowych Connect()metod, z dodatkowym parametrem o nazwie 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
Jesteś fanem GhostDoc, prawda? ;-) „Asynchronizuje połączenie” - klasyczny GhostDoc WTFness.
KeithS

1
:) tak, a czasami nawet nie czytelnik tego, co zostało wygenerowane.
picrap

Lepsze socket.EndConnect niż socket.Close?
Kiquenet,

3
socket.EndConnectTrwa ~ 10 sekund, aby zamknąć tak nie wróci funkcyjnych po okresie czasu, ale po horyzont czasowy + endConnect czasu
Royi Namir

8

Nie programuję w C #, ale w C rozwiązujemy ten sam problem, ustawiając gniazdo jako nieblokujące, a następnie umieszczając fd w pętli select / poll z wartością timeout równą ilości czasu, przez jaki jesteśmy gotowi czekać na połączenie odnieść sukces.

Znalazłem to dla Visual C ++ i wyjaśnienie tam również pochyla się w kierunku mechanizmu select / poll, który wyjaśniłem wcześniej.

Z mojego doświadczenia wynika, że ​​nie można zmienić wartości limitu czasu połączenia dla każdego gniazda. Możesz to zmienić dla wszystkich (przez dostrojenie parametrów systemu operacyjnego).


7

może być za późno, ale jest zgrabne rozwiązanie oparte na 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;
        }

Czy jakiekolwiek przeciążenie „ConnectAsync” akceptuje hosta i port?
marsh-wiggle

@ marsh-wiggle, metoda „ConnectAsync” ma 4 przeciążenia docs.microsoft.com/en-us/dotnet/api/ ... Zajrzyj do sekcji Metody rozszerzeń
Oleg Bondarenko

1
@OlegBondarenko ok, niedostępne dla .net 4.5.1. Muszę to sam owinąć. Dzięki!
marsh-wiggle

5

Rozwiązałem problem, używając metody Socket.ConnectAsync zamiast metody Socket.Connect. Po wywołaniu Socket.ConnectAsync (SocketAsyncEventArgs), uruchom licznik czasu (timer_connection), jeśli czas się skończył, sprawdź, czy połączenie gniazda jest połączone (jeśli (m_clientSocket.Connected)), jeśli nie, wyskakuje błąd limitu czasu.

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
Kiedy stoper się zatrzymuje, pojawia się komunikat o błędzie, prawda? Jak to powstrzymuje twój stos TCP przed faktycznym połączeniem. Wyobraź sobie scenariusz, w którym do zdalnego hosta jest więcej niż 2 sekundy, tj. Rto> 2. Twój licznik czasu zostanie zatrzymany, a Ty wydrukujesz komunikat o błędzie. Jednak protokół TCP nie jest kontrolowany przez zegar. Nadal będzie próbował się połączyć i po 2 sekundach może nawiązać połączenie. Czy C # zapewnia możliwość anulowania żądań „connect” lub zamknięcia gniazda. Twoje rozwiązanie czasowe jest równe sprawdzeniu po 2 sekundach, czy połączenie powiodło się.
Aditya Sehgal

Znalazłem to: splinter.com.au/blog/?p=28 Wygląda na to, że tak jest. Jest podobny do twojego, ale myślę, że robi to, co wyjaśniłem powyżej.
Aditya Sehgal

Po przekroczeniu limitu czasu należy wywołać m_clientSocket.Close ();
Vincent McNabb,

Zaktualizuj, mój link do bloga, do którego odwołuje się aditya, zmienił się: splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
Chris

Przepisałbym logikę związaną z wywołaniem "timer_connection.Dispose ();". Odwołanie do obiektu timer_connection jest prawdopodobnie używane po usunięciu obiektu.
BoiseBaked

2

Sprawdź to w MSDN . Nie wydaje się, że można to zrobić za pomocą zaimplementowanych właściwości w klasie Socket.

Plakat na MSDN faktycznie rozwiązał jego problem przy użyciu wątków. Miał główny wątek, który wywoływał inne wątki, które uruchamiają kod połączenia na kilka sekund, a następnie sprawdzają właściwość Connected gniazda:

Stworzyłem inną metodę, która faktycznie łączyła gniazdo ... miał główny wątek uśpiony na 2 sekundy, a następnie sprawdziłem metodę łączenia (która jest uruchamiana w osobnym wątku), czy gniazdo było dobrze podłączone, w przeciwnym razie wyrzuć wyjątek „Przekroczono limit czasu” i to wszystko. Jeszcze raz dziękuję za odpowiedzi.

Co próbujesz zrobić i dlaczego nie może czekać 15-30 sekund, zanim upłynie limit czasu?


2

Miałem ten sam problem podczas łączenia się z gniazdem i wpadłem na poniższe rozwiązanie, działa dobrze dla mnie. `

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

Pracowałem z Unity i miałem problem z BeginConnect i innymi metodami asynchronicznymi z gniazda.

Jest coś, czego nie rozumiem, ale przykłady kodu wcześniej nie działają.

Więc napisałem ten fragment kodu, aby działał. Testuję to w sieci adhoc z Androidem i komputerem, również lokalnie na moim komputerze. Mam nadzieję, że to pomoże.

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


}

i istnieje bardzo prosty watchdog w C #, aby to działało:

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

To jest jak odpowiedź FlappySock, ale dodałem do niej wywołanie zwrotne, ponieważ nie podobał mi się układ i sposób zwracania wartości logicznej. W komentarzach do tej odpowiedzi Nicka Millera:

Z mojego doświadczenia wynika, że ​​jeśli punkt końcowy jest osiągalny, ale na punkcie końcowym nie ma serwera, który mógłby odebrać połączenie, to AsyncWaitHandle.WaitOne zostanie zasygnalizowany, ale gniazdo pozostanie niepodłączone

Więc wydaje mi się, że poleganie na tym, co zostanie zwrócone, może być niebezpieczne - wolę używać socket.Connected. Ustawiam wartość logiczną dopuszczającą wartość null i aktualizuję ją w funkcji wywołania zwrotnego. Zauważyłem też, że nie zawsze kończy raportowanie wyniku przed powrotem do funkcji głównej - radzę sobie z tym i czekam na wynik używając limitu czasu:

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

Powiązane: Jak sprawdzić, czy mam połączenie?


-8

W klasie Socket powinna znajdować się właściwość ReceiveTimeout.

Właściwość Socket.ReceiveTimeout


1
Próbowałem. Po prostu nie działa. Dodałem m_clientSocket.ReceiveTimeout = 1000; przed wywołaniem m_clientSocket.Connect (ipEnd). Jednak nadal czeka około 15-20 sekund, zanim pojawi się komunikat o wyjątku.
ninikin

2
Ustawia limit czasu, w którym gniazdo odbiera dane po nawiązaniu połączenia.
eric.christensen

1
Nie można używać ReceiveTimeout- dotyczy to wyłącznie odbioru z BeginReceivei EndReceive. Nie ma odpowiednika, gdy po prostu widzisz, czy masz połączenie.
vapcguy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.