套接字服务器在一段时间后停止接受连接

4
我们有一个用C#编写的异步套接字服务器(运行在Windows Web Server 2008上)。它一直运行得很好,直到出现不明原因停止接受新连接的情况。我们平均有大约200个并发连接,但我们保持连接创建和连接断开的计数。这些数字可以高达10,000或低至只有1000,然后就会停止!有时它可以运行长达8个小时,有时仅半小时,目前它正在运行了一个小时左右,然后我们有另一个应用程序在无法连接时自动将其重新启动(这不是非常理想)。看起来我们并没有耗尽套接字,因为我们正常关闭它们,我们还记录所有错误,但在它停止之前没有发生任何事情。我们可以找出问题所在。有人有任何想法可能是怎么回事吗?我可以粘贴代码,但通常只是那些你在任何地方都能看到的异步beginaccept/send代码。

以下两个答案都非常好。我首先会检查监听套接字是否被垃圾回收了。然后等待错误发生,并使用TCPViewnetstat -a捕获所有套接字状态。 - Stephen Cleary
3个回答

5
客户端和服务器哪一个发起主动关闭?如果是服务器,则可能会在服务器上积累处于“TIME_WAIT”状态的套接字,这可能会阻止您接受新连接。如果客户端连接的持续时间很短,并且您经历了大量短暂的客户端连接发生的时期,则更有可能发生这种情况。
哦,如果您确实在“TIME_WAIT”中积累了套接字,请不要仅仅假设改变整个机器的等待时间长度是最好的或唯一的解决方案。

Len怀疑的实际问题是短暂端口的耗尽。如果您遇到这个问题,最好的解决方案可能是增加短暂端口范围。 - Stephen Cleary
我们已经增加了机器上短暂端口的范围,但这并没有产生任何影响。客户端启动主动关闭。 - Rob
如果客户端发起主动关闭,则服务器上不太可能收集到“TIME_WAIT”套接字,因此增加短暂端口范围不会产生任何影响。 - Len Holgate
将不得不调查AcceptAsync()方法,没有看到很多关于它如何工作的例子。顺便说一句,感谢您抽出时间来帮助,真的非常感激! - Rob
侦听队列长度为100,如果有更多的BeginAccepts而可能不需要它们会产生什么影响?我们有些怀疑将它们散布在各处。我们已阅读了所有关于SocketAsyncEventArgs的示例,几乎所有示例都只是简单的回显服务器,并没有正确演示缓冲。 - Rob
显示剩余15条评论

3

我相信OP遇到了我们也遇到的致命问题组合:

  1. 在接受连接后调用SslStream.AuthenticateAsServer一直阻塞,很可能是由于客户端在连接后退出,例如半开连接问题。此调用在底层发出同步读取,因此有可能会被阻塞。
  2. .NET在与初始化接受的线程相同的线程上同步调用传递给Socket.BeginAccept的回调函数,即您的服务器监听线程。这是完全意外的,但它们确实记录了这一点,请参见BeginAccept上的注释。

结合这些问题,您将得到以下事件序列:

  1. 您的主要监听线程调用 Socket.BeginAccept
  2. .NET 决定在监听线程上同步调用接受回调。
  3. 您的接受代码调用 SslStream.AuthenticateAsServer(或任何其他阻塞调用),并等待永远不会到来的响应... bingo,您的监听线程被永久阻塞!

我们通过以下方式解决了这个问题:

  • Set a ReceiveTimeout on the socket you get after accepting a connection. This prevents SslStream.AuthenticateAsServer, or any other sync read, from blocking forever.
  • Check whether the accept callback completed synchronously, and if so, turn around and manually spawn another thread to run the rest of your accept logic, so the listening thread is never tied up doing any processing. That is, pass a callback to BeginAccept that does something like this:

    private void AcceptCallbackWithSyncCheck(IAsyncResult asyncResult)
    {
        if (asyncResult.CompletedSynchronously)
        {
            // Force the accept logic to run async, to keep our listening
            // thread free.
            Action accept = () => this.ActualAcceptCallback(asyncResult);
    
            accept.BeginInvoke(accept.EndInvoke, null);
        }
        else
        {
            this.ActualAcceptCallback(asyncResult);
        }
    }
    

对于那些好奇的人,我们通过使用客户端模拟器进行大量同时调用服务,并在问题发生时使用Visual Studio的远程调试工具附加到服务进程来解决了这个问题。这使我们可以立即看到监听线程被阻塞的位置。然而,在花费了几周时间不断碰壁之后才做到这一点,因此,我希望这能帮助未来必须处理此类问题的可怜人们...


1
太棒了...我之前不知道SslStream.AuthenticateAsServer会一直阻塞(这导致我的代码无法执行,也让监听线程不能继续)。在接受连接的套接字上设置ReceiveTimeout同样解决了我的问题。 - bcook

1

没有看到代码,几乎不可能猜测。但我还是会尝试,脑海中想到的一件事是,您可能没有维护对监听套接字的引用,并且在某个时候GC收集了套接字,导致您的监听停止。

现在当然,这有时运行数小时使得这几乎不太可能是原因,但这是我想提及的一个值得注意的问题。


这很有意思,我们将把监听套接字设为全局变量,以确保它的正常工作。 - Rob
我无法在这里粘贴代码,因为有太多行代码了,但我们使用的是非常通用的代码。感觉像是某种配置问题,但我们可以解决它。 - Rob
不确定是否有人愿意,但我可以把一大块代码发给你看看? - Rob
为了提供更多信息,netstat -a没有返回任何TIME_WAIT套接字,而是返回了相当多的CLOSE_WAIT和ESTABLISHED,但没有time_wait。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接