如何防止插座/端口耗尽?

时间:2012-07-19 21:16:48

标签: c# multithreading sockets tcp

我试图通过跨越多个线程的请求来对网站进行性能测试。每个线程执行 n 次。 (在for循环中)

然而,我遇到了问题。特别是WebException(“无法连接到远程服务器”)与内部异常:

  

由于系统无法执行套接字上的操作   缺少足够的缓冲区空间或因为队列已满   127.0.0.1:52395

我试图在每个线程500次迭代时运行100个线程。

最初我在System.Net中使用HttpWebRequest向服务器发出GET请求。目前我正在使用WebClient,因为我假设每次迭代都使用新的套接字(因此在短时间内使用100 * 500个套接字)。我假设WebClient(每个线程实例化一次)只会使用一个套接字。

我不需要一次打开50 000个套接字,因为我想发送GET请求,接收响应并关闭套接字,释放它以便在下一个循环迭代中使用。我明白这将是一个问题

然而,即使使用WebClient,也会请求一堆套接字,导致TIME_WAIT模式下的一堆套接字(使用netstat检查)。这会导致其他应用程序(如互联网浏览器)挂起并停止运行。

我可以用更少的迭代和/或更少的线程来操作我的测试,因为看起来套接字最终会退出这个TIME_WAIT状态。但是,这不是一个解决方案,因为它没有充分测试Web服务器的功能。

问题:

如何在每次线程迭代后显式关闭套接字(从客户端)以防止TIME_WAIT状态和套接字耗尽?

代码:

包装HttpRequest的类

编辑:在使用中包装WebClient,因此每次迭代都会实例化,使用和处理新的WebClient。问题仍然存在。

  public sealed class HttpGetTest : ITest {
    private readonly string m_url;

    public HttpGetTest( string url ) {          
        m_url = url;
    }

    void ITest.Execute() {
        using (WebClient webClient = new WebClient()){
            using( Stream stream = webClient.OpenRead( m_url ) ) {          
            }
        }
    }
}

我的ThreadWrapperClass中创建新线程的部分:

public void Execute() {
    Action Hammer = () => {
        for( int i = 1; i <= m_iterations; i++ ) {
            //Where m_test is an ITest injected through constructor
            m_test.Execute();
        }       
    };
    ThreadStart work = delegate {
        Hammer();
    };
    Thread thread = new Thread( work );
    thread.Start();
}

5 个答案:

答案 0 :(得分:11)

你了解TIME_WAIT的目的吗?这是一段重复使用端口不安全的时期,因为之前的事务中丢失的数据包(已成功重新传输)可能会在该时间段内传递。

你可能会在某个地方的注册表中调整它,但我怀疑这是否是明智的下一步。

我在测试环境中创建实际负载的经验证明非常令人沮丧。当然从localhost运行你的负载测试器绝对不现实,我使用.net http apis进行的大多数网络测试似乎要求客户端比服务器本身更多的grunt。

因此,最好转移到第二台机器以在服务器上产生负载......但是,国内路由设备很少能够支持任何接近连接数量的任何地方,这会导致任何类型的负载。写得很好的服务器应用程序,所以现在你也需要升级你的路由/交换设备!

最后,围绕.net Http客户端API,我遇到了一些非常奇怪和意想不到的性能问题。在一天结束时,他们都使用HttpWebRequest来完成繁重的工作。 IMO它的性能远远不够。即使在异步调用API时,DNS也是同步的(尽管如果您只是从单个主机请求,这不是问题),并且在持续使用之后CPU使用量会逐渐增加,直到客户端变为CPU受限而不是IO约束为止。如果你想要产生持续和沉重的负载,任何依赖于HttpWebRequest的请求重的应用程序都是IMO的一项虚假投资。

总而言之,这是一项相当棘手的工作,而且最终只能在野外证明,除非你有足够的现金花在更好的装备上。

[提示:我使用异步Socket apis和第三方DNS客户端库编写了自己的客户端更好的性能]

答案 1 :(得分:2)

  

问:如何明确关闭套接字...以防止   TIME_WAIT状态?

A:老兄,TIME_WAIT是不可或缺的 - 而且很重要! - TCP / IP本身的一部分!

可以调整操作系统以减少TIME_WAIT(可能会产生负面影响)。

您可以调整操作系统以增加#/临时端口:

以下是关于TIME_WAIT存在的原因的链接......以及为什么这是一件好事:

答案 2 :(得分:1)

看起来你并没有强迫你的WebClient摆脱它已经分配的资源。您正在返回的流上执行“使用”,但您的WebClient仍有资源。

将WebClient实例化包装在using块中,或者在读完URL后手动调用dispose。

试试这个:

public sealed class HttpGetTest : ITest {
    private readonly string m_url;

    public HttpGetTest( string url ) {
        m_url = url;        
    }

    public void ITest.Execute() {
        using( var m_webClient = new WebClient())
        {
            using( Stream stream = m_webClient.OpenRead( m_url ) ) 
            {

            }
        }
    }
}

答案 3 :(得分:1)

关闭套接字或释放应用中的资源不是问题。 TIME _WAIT是已释放套接字上的TCP堆栈时间,以防止它们重新使用,直到从先前连接到该套接字的任何数据包“遗留”几乎不可能到期为止。

出于测试目的,您可以将等待时间从默认值(几分钟,AFAIK)减少到较小的值。在对服务器进行负载测试时,我将其设置为六秒钟。

它位于注册表的某处 - 如果你是Google,你会发现它。

找到它:

Change TIME_WAIT delay

答案 4 :(得分:0)

你不需要乱用TIME_WAIT来完成你想要的任务。

问题是每次调用Execute()时都要处理WebClient。执行此操作时,关闭与服务器的套接字连接,并且TCP端口在TIME_WAIT期间保持忙碌。

更好的方法是在HttpGetTest类的构造函数中创建WebClient,并在整个测试中重用相同的对象。

WebClient默认使用keep alive,并且会为其所有请求重复使用相同的连接,因此在您的情况下,只有100个打开的连接。