如何配置套接字连接超时

Posted

技术标签:

【中文标题】如何配置套接字连接超时【英文标题】:How to configure socket connect timeout 【发布时间】:2010-11-06 21:59:46 【问题描述】:

当客户端尝试连接断开连接的 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);

【问题讨论】:

【参考方案1】:

我找到了这个。比公认的答案更简单,并且适用于 .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.");


//... 

【讨论】:

好的,关于这个的输入很少——我喜欢这个并且它的代码少了很多。但是 !success 不是正确的条件。相反,添加 if (!_socket.Connected) 效果会更好。我会给它一个+1,因为少即是多。 取决于你的两个端点。如果他们都在数据中心,那么 1 秒应该足够了,3 秒是好的衡量标准,10 秒是善良的。如果一端在移动设备上,例如智能手机,那么您可能会看到 30 秒。 另一件事情也要注意...你是个例外。所以一定要检查... 如果我想增加超时而不是减少超时怎么办?我认为异步方法只是让您让代码不等待 20 秒(套接字连接中设置的内部超时)。但是如果连接需要更长的时间,BeginConnect 无论如何都会停止。或者,BeginConnect 是否在内部永远等待?我的连接速度非常慢,有时需要长达 30-40 秒才能连接,并且经常在 21 秒时超时。 @TravisWhidden 可以确认,这很重要!根据我的经验,如果可以到达端点,但端点上没有能够接收连接的服务器,则将向AsyncWaitHandle.WaitOne 发出信号,但套接字将保持未连接状态。【参考方案2】:

我的看法:

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.
        
    

【讨论】:

我冒昧地处理了一个条件。希望你不要介意。 Per cmets on top-rated answer,这看起来是一个副本,除了它被制成SocketExtension,您还没有使用.Connected 来查看您是否是,并且你没有使用socket.Connected = true; 来定义success【参考方案3】:

我刚刚编写了一个扩展类以允许连接超时。就像使用标准 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)
             
        
    

【讨论】:

GhostDoc 的粉丝,是吗? ;-) “异步连接” - 经典的 GhostDoc WTFness。 :) 是的,有时甚至不是生成的内容的读者。 socket.EndConnect 比 socket.Close 更好? socket.EndConnect 需要大约 10 秒才能关闭,因此函数不会在时间跨度之后而是在时间跨度+endConnect 时间之后返回【参考方案4】:

我没有用 C# 编程,但在 C 中,我们通过使套接字非阻塞然后将 fd 置于选择/轮询循环中来解决相同的问题,超时值等于我们愿意等待的时间量连接成功。

我发现 this 用于 Visual C++,那里的解释也倾向于我之前解释的选择/轮询机制。

根据我的经验,您无法更改每个套接字的连接超时值。您可以全部更改(通过调整操作系统参数)。

【讨论】:

【参考方案5】:

可能为时已晚,但有基于 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/… 请查看扩展方法部分 @OlegBondarenko 好的,不适用于 .net 4.5.1。我必须自己包起来。谢谢!【参考方案6】:

我通过使用 Socket.ConnectAsync 方法而不是 Socket.Connect 方法解决了这个问题。 调用Socket.ConnectAsync(SocketAsyncEventArgs)后,启动一个定时器(timer_connection),如果时间到了,检查socket连接是否连接(if(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();
        
    

【讨论】:

当计时器停止时,您会显示错误消息对吗?这如何阻止您的 TCP 堆栈实际连接。想象一个场景,远程主机距离超过 2 秒,即 rto > 2。您的计时器将停止,您将打印错误消息。但是,TCP 不受您的计时器控制。它仍然会尝试连接,并且可能会在 2 秒后成功连接。 C# 是否提供了取消“连接”请求或关闭套接字的功能。您的计时器解决方案等于在 2 秒后检查连接是否成功。 我发现了这个:splinter.com.au/blog/?p=28 看起来就是这样。它与您的相似,但我认为它与我上面解释的一样。 超时时,应调用m_clientSocket.Close(); 更新,aditya 引用的我的博客链接已更改:splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t 我会重写与调用“timer_connection.Dispose();”相关的逻辑。 timer_connection 对象引用可能在对象被释放后使用。【参考方案7】:

在MSDN 上查看。您似乎无法使用 Socket 类中已实现的属性来执行此操作。 MSDN 上的海报实际上solved his problem 使用线程。他有一个主线程调用其他线程,这些线程运行连接代码几秒钟,然后检查套接字的 Connected 属性:

我实际上创建了另一种方法 连接插座...有主 线程休眠 2 秒,然后 检查连接方法(至 在单独的线程中运行)如果 插座连接良好,否则 抛出异常“超时”和 就这样。再次感谢 回复。

你想做什么,为什么不能等待 15-30 秒才超时?

【讨论】:

【参考方案8】:

我在连接到 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)
           

           
        

【讨论】:

【参考方案9】:

我使用 Unity,但在使用 BeginConnect 和来自套接字的其他异步方法时遇到了一些问题。

有些东西我不明白,但之前的代码示例对我不起作用。

所以我写了这段代码来让它工作。我在带有 android 和 pc 的 adhoc 网络上测试它,也在我的计算机上本地。希望对您有所帮助。

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://***.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);
    

【讨论】:

【参考方案10】:

这就像 FlappySock 的答案,但我添加了一个回调,因为我不喜欢布局以及布尔值是如何返回的。在尼克米勒的回答中:

以我的经验,如果可以到达端点,但端点上没有服务器能够接收连接,则 AsyncWaitHandle.WaitOne 将发出信号,但套接字将保持未连接状态

所以对我来说,依赖返回的内容似乎很危险——我更喜欢使用socket.Connected。我设置了一个可为空的布尔值并在回调函数中更新它。我还发现它并不总是在返回主函数之前完成报告结果 - 我也处理了这个,并使用超时让它等待结果:

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 
    

相关: How to check if I'm connected?

【讨论】:

【参考方案11】:

Socket 类中应该有一个 ReceiveTimeout 属性。

Socket.ReceiveTimeout Property

【讨论】:

我试过了。它只是行不通。我添加了 m_clientSocket.ReceiveTimeout = 1000;在调用 m_clientSocket.Connect(ipEnd) 之前。但是,它仍然会等待大约 15-20 秒,然后才会弹出异常消息。 设置连接建立后套接字接收数据的超时时间。 不能使用ReceiveTimeout - 这仅适用于使用BeginReceiveEndReceive 接收时。当您只是查看您是否已连接时,没有等价物。

以上是关于如何配置套接字连接超时的主要内容,如果未能解决你的问题,请参考以下文章

如何在Spring Data Elasticsearch 2.2.3中配置Rest高级客户端的套接字超时

如何在 reactor-netty 中配置池连接空闲超时

建立多个连接时如何在C中设置套接字超时?

如何设置socket的Connect超时

如何设置数据库的连接数和连接超时时间

如何设定ClientSocket的超时时间。