如何配置套接字连接超时


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

Answers:


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。
TravisWhidden 2011年

2
取决于您的两个终点。如果它们都在数据中心中,那么1秒钟应该足够,3秒钟可以很好地测量,10秒钟可以。如果一端在移动设备(例如智能手机)上,则可能需要30秒。
FlappySocks 2012年

3
另一件事也看出来了...。如果不是把null在为callback你打算EndConnect(),如果套接字已经closed那么这会给你一个例外。所以请确保您检查...
poy

9
如果我想增加超时而不是减少超时怎么办?我认为异步方法只允许您使代码不等待20秒(在套接字连接中设置内部超时)。但是,如果连接花费更长的时间,则BeginConnect仍将停止。还是BeginConnect在内部永远等待?我的连接速度很慢,有时需要30-40秒才能连接,而21秒的超时经常发生。
亚历克斯

3
@TravisWhidden可以确认,这非常重要!以我的经验,如果可以到达终点,但是端点上没有服务器可以接收连接,AsyncWaitHandle.WaitOne则将发出信号,但套接字将保持未连接状态。
Nicholas Miller

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 2012年

根据对最高评价答案的评论,该评论看起来像是对它的复制,只是将其制成了SocketExtension,您仍然不习惯.Connected查看自己是否在使用,也不习惯使用socket.Connected = true;define 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 2011年

1
:)是的,有时甚至连读者都不知道所生成的内容。
picrap 2011年

socket.EndConnect比socket.Close好吗?
Kiquenet

3
socket.EndConnect大约需要10秒的时间间隔后,不能不时间跨度+ endConnect时间后封闭,因此该函数返回
Royi纳米尔

8

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

我在Visual C ++中发现了这一点,那里的解释也倾向于我之前解释的选择/轮询机制。

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


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/…请查看“扩展方法”部分
Oleg Bondarenko

1
@OlegBondarenko好的,.net 4.5.1不可用。我必须自己把它包起来。谢谢!
marsh-wiggle

5

我通过使用Socket.ConnectAsync方法而不是Socket.Connect方法解决了该问题。调用Socket.ConnectAsync(SocketAsyncEventArgs)后,启动计时器(timer_connection),如果时间到了,请检查套接字连接是否已连接(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();
        }
    }

2
当计时器停止时,您显示一条错误消息对吗?这如何阻止您的TCP堆栈实际连接。想象一下一个场景,其中远程主机超过2秒,即rto>2。您的计时器将停止并且您将显示错误消息。但是,TCP不受计时器的控制。它仍然会尝试连接,并可能在2秒后成功连接。C#是否提供取消“连接”请求或套接字关闭的功能。您的计时器解决方案等于2秒钟后检查连接是否成功。
Aditya Sehgal

我发现了:splinter.com.au/blog/?p=28 看起来就是这样。它与您的相似,但我认为它确实满足了我上面的解释。
Aditya Sehgal

超时时,应调用m_clientSocket.Close();。
Vincent McNabb

更新,如阿迪蒂亚引用我的博客链接已经改变:splinter.com.au/opening-a-tcp-connection-in-c-with-a-custom-t
克里斯-

我将重新编写与调用“ timer_connection.Dispose();”有关的逻辑。放置对象后,可能会使用timer_connection对象引用。
博伊西于

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和套接字上的其他异步方法遇到了一些问题。

有些事情我不了解,但是之前的代码示例对我不起作用。

因此,我编写了这段代码以使其起作用。我在带有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。我设置了一个可为空的布尔值,并在回调函数中对其进行了更新。我还发现在返回到主函数之前,它并不总是完成报告结果的过程-我也处理这一点,并使其使用超时来等待结果:

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

套接字类中应该有一个ReceiveTimeout属性。

Socket.ReceiveTimeout属性


1
我试过了。就是行不通。我添加了m_clientSocket.ReceiveTimeout = 1000; 在调用m_clientSocket.Connect(ipEnd)之前。但是,它仍然等待大约15-20秒,然后弹出异常消息。
ninikin 2009年

2
这设置了建立连接后套接字正在接收数据的超时时间。
eric.christensen 2009年

1
无法使用ReceiveTimeout-这是与BeginReceiveand一起接收时的严格要求EndReceive。当您仅查看是否已连接时,没有任何等效的选择。
vapcguy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.