使用System.Net.Sockets.Socket.AcceptAsync模型时堆栈溢出

时间:2010-10-17 18:37:14

标签: c# sockets asynchronous

对于C#和.NET的System.Net.Sockets.Socket.AcceptAsync方法,需要处理返回值“false”以便从同步处理的连接中处理立即可用的SocketAsyncEventArgs状态。 Microsoft提供了示例(在System.Net.Sockets.SocketAsyncEventArgs类页面上找到),如果存在大量挂起连接,则会导致堆栈溢出,这可以在任何实现其处理模型的系统上利用。

解决此问题的其他想法是创建一个调用处理程序方法的循环,条件是值Socket.AcceptAsync返回等于false,并打破循环(允许延迟处理)如果该值指示操作正在异步完成(true)。但是,此解决方案还会导致堆栈溢出漏洞,因为与传递给SocketAsyncEventArgs的{​​{1}}相关联的回调在方法结束时调用Socket.AcceptAsync,还有一个循环,用于立即可用,同步接受的连接。

正如您所看到的,这是一个非常可靠的问题,我还没有找到一个不涉及Socket.AcceptAsync并创建大量其他方法和调度处理的好解决方案。据我所知,与System.Threading.ThreadPool相关的异步套接字模型需要的内容要多于MSDN上的示例所示。

有没有人有一个干净而有效的解决方案来处理从Socket.AcceptAsync同步接受的紧急挂起连接,而无需创建单独的线程来处理连接而不使用递归?

4 个答案:

答案 0 :(得分:7)

我不会使用AcceptAsync,而是使用BeginAccept / EndAccept,并正确实现常见的异步模式,即检查CompletedSynchronously以避免回调完成操作的回调线程。

另见AsyncCallBack CompletedSynchronously


关于使用AcceptAsync的要求的修改:

MSDN documentation明确表示不会为同步完成的操作调用回调。这与始终调用回调的常见异步模式不同。

  

如果I / O操作是,则返回true   悬而未决。该   SocketAsyncEventArgs.Completed事件   关于e参数将被提出   完成手术。返回   如果I / O操作完成,则为false   同步。该   SocketAsyncEventArgs.Completed事件   在e参数上不会引发   并且e对象作为参数传递   可以在之后立即检查   方法调用返回以检索   操作的结果。

我目前看不到循环如何解决堆栈溢出问题。也许你可以更具体地解决导致问题的代码?


编辑2:我正在考虑这样的代码(仅针对AcceptAsync,其余的只是为了让一个有效的应用程序试用它):

static void Main(string[] args) {
    Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 4444));
    listenSocket.Listen(100);
    SocketAsyncEventArgs e = new SocketAsyncEventArgs();
    e.Completed += AcceptCallback;
    if (!listenSocket.AcceptAsync(e)) {
        AcceptCallback(listenSocket, e);
    }
    Console.ReadKey(true);
}

private static void AcceptCallback(object sender, SocketAsyncEventArgs e) {
    Socket listenSocket = (Socket)sender;
    do {
        try {
            Socket newSocket = e.AcceptSocket;
            Debug.Assert(newSocket != null);
            // do your magic here with the new socket
            newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!"));
            newSocket.Disconnect(false);
            newSocket.Close();
        } catch {
            // handle any exceptions here;
        } finally {
            e.AcceptSocket = null; // to enable reuse
        }
    } while (!listenSocket.AcceptAsync(e));
}

答案 1 :(得分:3)

我通过简单地改变循环的位置来解决这个问题。不是从内部递归调用accept处理程序,而是将代码包装在条件为“!Socket.AcceptAsync(args)”的do-while循环中,以防止堆栈溢出。

这背后的原因是你在利用异步等待其他连接遇到麻烦之前,利用回调线程来处理立即可用的连接。它有效地重用了一个汇集的线程。

我很欣赏这些回复,但由于某些原因,他们都没有点击我,也没有真正解决问题。但是,似乎有些东西引发了我的想法,想出了这个想法。它避免了手动使用ThreadPool类,也不使用递归。

当然,如果有人有更好的解决方案,甚至是替代方案,我会很高兴听到它。

答案 2 :(得分:1)

我没有仔细看过,但闻起来有点像这可能会有所帮助(参见“堆叠潜水”一节):

http://blogs.msdn.com/b/mjm/archive/2005/05/04/414793.aspx

答案 3 :(得分:0)

newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
newSocket.Disconnect(false); 
newSocket.Close(); 

上面这段代码的问题在于,这会阻止您的下一次接受操作。

更好的方法是这样的:

while (true)
{
   if (e.SocketError == SocketError.Success)
   {
      //ReadEventArg object user token
      SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
      Socket socket = ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;

      if (!socket.ReceiveAsync(readEventArgs))
         ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessReceiveEx), readEventArgs); .
    }
    else
    {
       HadleBadAccept(e);
    }

    e.AcceptSocket = null;

    m_maxNumberAcceptedClients.WaitOne();
    if (listenSocket.AcceptAsync(e))
       break;
}