So konfigurieren Sie das Socket-Verbindungszeitlimit

104

Wenn der Client versucht, eine Verbindung zu einer nicht verbundenen IP-Adresse herzustellen, tritt eine lange Zeitüberschreitung von mehr als 15 Sekunden auf. Wie können wir diese Zeitüberschreitung verringern? Wie kann ich es konfigurieren?

Der Code, den ich zum Einrichten einer Socket-Verbindung verwende, lautet wie folgt:

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
quelle

Antworten:

146

Ich habe das gefunden. Einfacher als die akzeptierte Antwort und funktioniert mit .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
quelle
20
OK, wenig Input zu diesem - Ich mag das und es ist viel weniger Code. Allerdings ist der Erfolg nicht die richtige Bedingung. Fügen Sie stattdessen if (! _Socket.Connected) hinzu und es funktioniert viel besser. Ich gebe ihm eine +1 für weniger ist mehr Aspekt.
TravisWhidden
2
Hängt von Ihren beiden Endpunkten ab. Wenn sich beide in einem Rechenzentrum befinden, sollte 1 Sekunde ausreichend sein, 3 für ein gutes Maß, 10 für eine freundliche. Wenn sich ein Ende auf einem mobilen Gerät wie einem Smartphone befindet, sehen Sie möglicherweise 30 Sekunden.
FlappySocks
3
Eine andere Sache zu halten Sie Ausschau nach ... Wenn anstatt, nullin für die callbackund Sie planen EndConnect(), wenn der Sockel wurde closeddann dies wird Ihnen eine Ausnahme. Also stellen Sie sicher, dass Sie überprüfen ...
poy
9
Was ist, wenn ich das Zeitlimit ERHÖHEN möchte, anstatt es zu VERRINGERN? Ich denke, mit dem asynchronen Ansatz können Sie den Code nur 20 Sekunden lang warten lassen (das in Socket Connect festgelegte interne Zeitlimit). Falls die Verbindung jedoch länger dauert, wird BeginConnect trotzdem gestoppt. Oder wartet BeginConnect intern für immer? Ich habe eine sehr langsame Verbindung, wenn manchmal bis zu 30-40 Sekunden benötigt werden, um eine Verbindung herzustellen, und Zeitüberschreitungen bei der 21. Sekunde treten sehr häufig auf.
Alex
3
@TravisWhidden Kann bestätigen, das ist sehr wichtig! Nach meiner Erfahrung AsyncWaitHandle.WaitOnewird signalisiert , ob der Endpunkt erreicht werden kann, aber auf dem Endpunkt kein Server vorhanden ist, der die Verbindung empfangen kann, aber der Socket bleibt nicht verbunden.
Nicholas Miller
29

Meine Einstellung:

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
quelle
Ich habe mir erlaubt, mit einer Krankheit umzugehen. Hoffe es macht dir nichts aus.
Hemant
Laut Kommentaren zu der am besten bewerteten Antwort, von der dies eine Kopie zu sein scheint, mit der Ausnahme, dass sie zu einer gemacht wurde SocketExtension, haben Sie immer noch nicht .Connectedgesehen, ob Sie es sind, und Sie verwenden sie nicht socket.Connected = true;zum Definieren success.
Vapcguy
22

Ich habe gerade eine Erweiterungsklasse geschrieben, um Zeitüberschreitungen in Verbindungen zuzulassen. Verwenden Sie es genau so, wie Sie es mit den Standardmethoden Connect()tun würden, mit einem zusätzlichen Parameter namens 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
quelle
8
Fan von GhostDoc, oder? ;-) "Asyncs the connect" - klassische GhostDoc WTFness.
KeithS
1
:) ja, und manchmal nicht einmal ein Leser von dem, was generiert wurde.
Picrap
Besser socket.EndConnect als socket.Close?
Kiquenet
3
Das socket.EndConnectSchließen dauert ~ 10 Sekunden, sodass die Funktion nicht nach der Zeitspanne, sondern nach der Zeitspanne + der EndConnect-Zeit zurückkehrt
Royi Namir
8

Ich programmiere nicht in C #, aber in C lösen wir das gleiche Problem, indem wir den Socket nicht blockieren und dann den fd in eine Select / Poll-Schleife mit einem Timeout-Wert setzen, der der Zeit entspricht, die wir bereit sind, auf die Verbindung zu warten erfolgreich sein.

Ich fand diese für Visual C ++ gefunden und die Erklärung dort geht auch in Richtung des zuvor erläuterten Auswahl- / Abfragemechanismus.

Nach meiner Erfahrung können Sie die Werte für das Verbindungszeitlimit pro Socket nicht ändern. Sie ändern es für alle (durch Einstellen der Betriebssystemparameter).

Aditya Sehgal
quelle
7

Es ist vielleicht zu spät, aber es gibt eine gute Lösung, die auf Task.WaitAny basiert (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
quelle
Akzeptiert eine "ConnectAsync" -Überlastung Host und Port?
Sumpf wackeln
@ marsh-wiggle, die "ConnectAsync" -Methode hat 4 Überladungen docs.microsoft.com/en-us/dotnet/api/… Bitte lesen Sie den Abschnitt "Erweiterungsmethoden"
Oleg Bondarenko
1
@OlegBondarenko ok, nicht verfügbar für .net 4.5.1. Ich muss es selbst einwickeln. Vielen Dank!
Marsh-Wiggle
5

Ich habe das Problem mithilfe der Socket.ConnectAsync-Methode anstelle der Socket.Connect-Methode gelöst. Starten Sie nach dem Aufrufen von Socket.ConnectAsync (SocketAsyncEventArgs) einen Timer (timer_connection). Wenn die Zeit abgelaufen ist, überprüfen Sie, ob eine Socket-Verbindung hergestellt ist (falls (m_clientSocket.Connected)), falls nicht, Popup-Timeout-Fehler.

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
quelle
2
Wenn der Timer stoppt, wird eine Fehlermeldung angezeigt, oder? Wie verhindert dies, dass Ihr TCP-Stack tatsächlich eine Verbindung herstellt? Stellen Sie sich ein Szenario vor, in dem ein Remote-Host mehr als 2 Sekunden entfernt ist, dh bis> 2. Ihr Timer stoppt und Sie drucken die Fehlermeldung. TCP wird jedoch nicht von Ihrem Timer gesteuert. Es wird weiterhin versucht, eine Verbindung herzustellen, und möglicherweise wird die Verbindung nach 2 Sekunden erfolgreich hergestellt. Bietet C # eine Möglichkeit zum Abbrechen von "Verbindungs" -Anforderungen oder zum Schließen eines Sockets? Ihre Timer-Lösung entspricht der Überprüfung nach 2 Sekunden, ob die Verbindung erfolgreich war.
Aditya Sehgal
Ich habe folgendes gefunden: splinter.com.au/blog/?p=28 Sieht so aus. Es ist ähnlich wie deins, aber ich denke, es macht das, was ich oben erklärt habe.
Aditya Sehgal
Wenn das Zeitlimit überschritten ist, sollten Sie m_clientSocket.Close () aufrufen.
Vincent McNabb
Update, wie mein Blog Link von aditya verwiesen hat sich geändert: splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
Chris
Ich würde die Logik für den Aufruf "timer_connection.Dispose ();" neu schreiben. Die Objektreferenz timer_connection wird möglicherweise verwendet, nachdem das Objekt entsorgt wurde.
BoiseBaked
2

Überprüfen Sie dies auf MSDN . Es scheint nicht, dass Sie dies mit den implementierten Eigenschaften in der Socket-Klasse tun können.

Das Poster auf MSDN hat sein Problem tatsächlich durch Threading gelöst . Er hatte einen Haupt-Thread, der andere Threads aufrief, die den Verbindungscode einige Sekunden lang ausführten und dann die Connected-Eigenschaft des Sockets überprüften:

Ich habe eine andere Methode erstellt, die den Socket tatsächlich verbunden hat ... hatte den Haupt-Thread 2 Sekunden lang im Ruhezustand und überprüfe dann die Verbindungsmethode (die in einem separaten Thread ausgeführt wird), ob der Socket gut verbunden war, andernfalls wird eine Ausnahme "Zeitüberschreitung" ausgelöst. und das ist alles. Nochmals vielen Dank für die Repleies.

Was versuchst du zu tun und warum kann es nicht 15 bis 30 Sekunden warten, bevor das Zeitlimit überschritten wird?

eric.christensen
quelle
2

Ich hatte das gleiche Problem beim Anschließen an eine Steckdose und fand die folgende Lösung: Es funktioniert gut für mich. `

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

Ich habe mit Unity gearbeitet und hatte ein Problem mit BeginConnect und anderen asynchronen Methoden von Socket.

Es gibt etwas, das ich nicht verstehe, aber die Codebeispiele zuvor funktionieren bei mir nicht.

Also habe ich diesen Code geschrieben, damit er funktioniert. Ich teste es in einem Ad-hoc-Netzwerk mit Android und PC, auch lokal auf meinem Computer. Hoffe es kann helfen.

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


}

und es gibt einen sehr einfachen Watchdog auf C #, damit es funktioniert:

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

Dies ist wie die Antwort von FlappySock, aber ich habe einen Rückruf hinzugefügt, weil mir das Layout und die Rückgabe des Booleschen Werts nicht gefallen haben. In den Kommentaren dieser Antwort von Nick Miller:

Nach meiner Erfahrung wird AsyncWaitHandle.WaitOne signalisiert, wenn der Endpunkt erreicht werden kann, aber kein Server auf dem Endpunkt die Verbindung empfangen kann, aber der Socket bleibt nicht verbunden

Für mich scheint es gefährlich zu sein, sich auf das zurückzugeben, was zurückgegeben wird - ich bevorzuge es zu verwenden socket.Connected. Ich setze einen nullbaren Booleschen Wert und aktualisiere ihn in der Rückruffunktion. Ich habe auch festgestellt, dass das Melden des Ergebnisses nicht immer abgeschlossen ist, bevor ich zur Hauptfunktion zurückkehre. Ich kümmere mich auch darum und lasse es mithilfe des Timeouts auf das Ergebnis warten:

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

Verwandte: Wie überprüfe ich, ob ich verbunden bin?

vapcguy
quelle
-8

In der Socket-Klasse sollte eine ReceiveTimeout-Eigenschaft vorhanden sein.

Socket.ReceiveTimeout-Eigenschaft

Colin
quelle
1
Ich habe es versucht. Es funktioniert einfach nicht. Ich habe m_clientSocket.ReceiveTimeout = 1000 hinzugefügt. vor dem Aufruf der m_clientSocket.Connect (ipEnd). Es wartet jedoch noch etwa 15 bis 20 Sekunden, bevor die Ausnahmemeldung angezeigt wird.
Ninikin
2
Hiermit wird das Zeitlimit festgelegt, zu dem der Socket nach dem Herstellen der Verbindung Daten empfängt.
eric.christensen
1
Kann nicht verwendet werden ReceiveTimeout- dies gilt ausschließlich für den Empfang mit BeginReceiveund EndReceive. Es gibt kein Äquivalent dafür, wenn Sie nur sehen, ob Sie verbunden sind.
Vapcguy