2009-02-22 184 views
4

我使用的SocketAsyncEventArgs看着一個服務器的源代碼,我試圖找出如何這會不會導致堆棧溢出:這不會導致堆棧溢出?

所以這段代碼被稱爲允許插座接受傳入連接(向下滾動至底部,明白我的意思):

/// <summary> 
/// Begins an operation to accept a connection request from the client. 
/// </summary> 
/// <param name="acceptEventArg">The context object to use when issuing 
/// the accept operation on the server's listening socket.</param> 
private void StartAccept(SocketAsyncEventArgs acceptEventArg) 
{ 
    if (acceptEventArg == null) 
    { 
     acceptEventArg = new SocketAsyncEventArgs(); 
     acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted); 
    } 
    else 
    { 
     // Socket must be cleared since the context object is being reused. 
     acceptEventArg.AcceptSocket = null; 
    } 

    this.semaphoreAcceptedClients.WaitOne(); 
    Boolean willRaiseEvent = this.listenSocket.AcceptAsync(acceptEventArg); 
    if (!willRaiseEvent) 
    { 
     this.ProcessAccept(acceptEventArg); 
    } 
} 

然後這個代碼被調用一次的連接實際上是接受(見最後一行):

/// <summary> 
     /// Process the accept for the socket listener. 
     /// </summary> 
     /// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param> 
     private void ProcessAccept(SocketAsyncEventArgs e) 
     { 
      if (e.BytesTransferred > 0) 
      { 
       Interlocked.Increment(ref this.numConnectedSockets); 
       Console.WriteLine("Client connection accepted. There are {0} clients connected to the server", 
        this.numConnectedSockets); 
      } 

      // Get the socket for the accepted client connection and put it into the 
      // ReadEventArg object user token. 
      SocketAsyncEventArgs readEventArgs = this.readWritePool.Pop(); 
      readEventArgs.UserToken = e.AcceptSocket; 

      // As soon as the client is connected, post a receive to the connection. 
      Boolean willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs); 
      if (!willRaiseEvent) 
      { 
       this.ProcessReceive(readEventArgs); 
      } 

      // Accept the next connection request. 
      this.StartAccept(e); // <==== tail end recursive? 
     } 

看看最後一個線。它再次調用頂層函數。這是如何通過在這兩個函數之間來回跳動來溢出堆棧的?這似乎是尾端遞歸,但這不是Haskell,所以我不明白這是如何工作的。

這是我的理解,這些不是在線程中被解僱,而是由cpu一次執行一個。

回答

1

如果無法立即滿足AsyncAccept(或任何AsyncXXX操作),則它將返回true,表示操作將異步完成。發生這種情況時,回調事件最終會在線程池線程上觸發。即使它編組回到UI線程(因爲它是在那裏啓動的),它也會通過一個帖子來完成。

AsyncAccept是高度有可能返回true,因爲除非有真正的掛起(見Listen積壓),你正在等待一個客戶端連接套接字連接。

因此,StartAccept()只會在不調用ProcessAccept的情況下退出,並且ProcessAccept在(和if)觸發時會簡單地退出,可能會在不同的線程中。

1

看代碼:

if (!willRaiseEvent) 
{ 
    this.ProcessAccept(acceptEventArg); 
} 

雖然我還不瞭解整個機制,willRaiseEvent == true,將清楚地結束遞歸,所以我想這種情況發生,所以它不是無限遞歸。

0

這一切都取決於willCauseEvent標誌,當設置爲true時會中斷遞歸。 如果沒有連接掛起,那麼該標誌可能設置爲true。

0

硬從上下文來確定,但看起來像第一隻調用第二個時直接

Boolean willRaiseEvent = this.listenSocket.AcceptAsync(acceptEventArg);  
if (!willRaiseEvent)  
{ 

,因此我猜大部分提出了在完成一個事件的時間(從不同的回調線)?

(另請參閱

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

這似乎是一個類似這樣的事情了「堆潛水」部分。)

0

ProcessAccept()總是電話StartAccept()但反之則不然。

StartAccept()ProcessAccept()只有在willRaiseEvent設置爲true時才被調用。那就是你從那裏的無限遞歸中退出。

如果您懷疑無限遞歸(無論是單遞歸函數還是其中一個乒乓函數),您都會查找可能的退出點,正如您雄辯地說的那樣:-)。