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

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

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

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

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

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

另请参见AsyncCallBack CompletedSynchronously


编辑有关使用AcceptAsync的要求:

MSDN文档明确指出,不会为同步完成的操作调用回调。 这与始终调用回调的常见异步模式不同。

如果I / O操作处于挂起状态,则返回true。 操作完成后,将引发e参数上的SocketAsyncEventArgs.Completed事件。 如果I / O操作同步完成,则返回false。 不会引发e参数上的SocketAsyncEventArgs.Completed事件,并且可以在方法调用返回后立即检查作为参数传递的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)); } 

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

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

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

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

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

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

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