2009-07-23 23 views
18

我有此代碼後...停止一個的TcpListener調用BeginAcceptTcpClient

internal static void Start() 
{ 
    TcpListener listenerSocket = new TcpListener(IPAddress.Any, 32599); 
    listenerSocket.Start(); 
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
} 

然後我的回調函數看起來像這樣...

private static void AcceptClient(IAsyncResult asyncResult) 
{ 
    MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult)); 
    ThreadPool.QueueUserWorkItem((object state) => handler.Process()); 
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
} 

現在,我打電話BeginAcceptTcpClient,然後一段時間後來我想停止服務器。爲此,我一直在調用TcpListener.Stop()或TcpListener.Server.Close()。這兩個都執行我的AcceptClient函數。當我調用EndAcceptTcpClient時,這會引發異常。什麼是最好的實踐方式呢?一旦我呼叫停止,我可以放一個標誌來阻止AcceptClient的執行,但是我想知道我是否錯過了一些東西。

更新1

目前我已經通過改變代碼看起來像這樣修補它。

private static void AcceptClient(IAsyncResult asyncResult) 
{ 
    if (!shutdown) 
    { 
      MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult)); 
      ThreadPool.QueueUserWorkItem((object state) => handler.Process()); 
      listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
    } 
} 

private static bool shutdown = false; 
internal static void Stop() 
{ 
    shutdown = true; 
    listenerSocket.Stop(); 
} 

更新2

我改成了impliment從斯賓塞Ruport答案。

private static void AcceptClient(IAsyncResult asyncResult) 
{ 
    if (listenerSocket.Server.IsBound) 
    { 
      MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult)); 
      ThreadPool.QueueUserWorkItem((object state) => handler.Process()); 
      listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null); 
    } 
} 
+1

您不必排隊另一個線程來完成處理工作;您可以調用EndAcceptTcpClient來告訴偵聽器處理它,然後在它之後調用BeginAcceptTcpClient來安排另一個處理。使用當前線程處理您剛收到的請求。 – 2010-11-26 15:42:46

回答

15

我剛剛遇到了這個問題,我相信你目前的解決方案不完整/不正確。檢查IsBound和隨後致電EndAcceptTcpClient()之間不保證原子性。如果偵聽器在這兩個語句之間爲Stop()'d,您仍然可以獲得異常。你沒有說你得到了什麼異常,但我認爲它是我得到的,ObjectDisposedException(抱怨底層套接字已經被丟棄)。

您應該能夠通過模擬線程調度進行檢查:

  • 坐落在IsBound檢查後再行斷點在回調
  • 凍結命中斷點(線程窗口的話題 - >右鍵點擊「凍結」)
  • 運行/觸發調用的代碼TcpListener.Stop()
  • 中斷並逐步調用EndAcceptTcpClient()調用。你應該看到ObjectDisposedException

IMO的理想解決方案是微軟在這種情況下拋出一個不同的例外,例如EndAcceptTcpClient,例如, ListenCanceledException或類似的東西。

事實上,我們必須從ObjectDisposedException推斷出發生了什麼。只要趕上例外情況並相應地行事。在我的代碼中,我默默地吃掉了這個異常,因爲我的代碼在其他地方正在進行真正的關閉工作(即首先調用TcpListener.Stop()的代碼)。無論如何,您應該已經在該區域進行異常處理,因爲您可以獲得各種SocketExceptions。這只是將另一個catch處理程序添加到該try塊中。

我承認我這個方法不舒服,因爲原則漁獲可能是假陽性,與真正的「壞」的目標在那裏訪問。但另一方面,EndAcceptTcpClient()調用中沒有太多對象訪問,否則可能觸發此異常。我希望。

這是我的代碼。這是早期/原型的東西,忽略控制檯調用。

private void OnAccept(IAsyncResult iar) 
    { 
     TcpListener l = (TcpListener) iar.AsyncState; 
     TcpClient c; 
     try 
     { 
      c = l.EndAcceptTcpClient(iar); 
      // keep listening 
      l.BeginAcceptTcpClient(new AsyncCallback(OnAccept), l); 
     } 
     catch (SocketException ex) 
     { 
      Console.WriteLine("Error accepting TCP connection: {0}", ex.Message); 

      // unrecoverable 
      _doneEvent.Set(); 
      return; 
     } 
     catch (ObjectDisposedException) 
     { 
      // The listener was Stop()'d, disposing the underlying socket and 
      // triggering the completion of the callback. We're already exiting, 
      // so just return. 
      Console.WriteLine("Listen canceled."); 
      return; 
     } 

     // meanwhile... 
     SslStream s = new SslStream(c.GetStream()); 
     Console.WriteLine("Authenticating..."); 
     s.BeginAuthenticateAsServer(_cert, new AsyncCallback(OnAuthenticate), s); 
    } 
+0

是的,我結束了在我打電話結束之前設置的一個停止布爾。然後我在調用end accept之前檢查這個。 – 2009-08-05 13:46:42

7

不,你不會錯過任何東西。您可以檢查Socket對象的IsBound屬性。至少對於TCP連接,當套接字正在監聽時,它將被設置爲true,並且在你關閉它之後它的值將是false。儘管如此,你自己的實現也可以工作。

+0

太棒了,這正是我一直在尋找的!謝謝! – 2009-07-23 19:42:18

+0

這是_答案;只需在異步回調中檢查「listener.Server.IsBound」,如果它是false,則返回。無需調用EndAccept *,然後捕獲(預期的和記錄的)異常。 – 2012-03-27 18:18:09

1

試試這個。它適用於我,沒有捕捉異常。

private void OnAccept(IAsyncResult pAsyncResult) 
{ 
    TcpListener listener = (TcpListener) pAsyncResult.AsyncState; 
    if(listener.Server == null) 
    { 
     //stop method was called 
     return; 
    } 
    ... 
} 
0

我認爲這需要所有的樹的事情,並且BeginAcceptTcpClient重啓應放在EndAcceptTcpClient的tryctach之外。

private void AcceptTcpClientCallback(IAsyncResult ar) 
    { 
     var listener = (TcpListener)ar.AsyncState; 

     //Sometimes the socket is null and somethimes the socket was set 
     if (listener.Server == null || !listener.Server.IsBound) 
      return; 

     TcpClient client = null; 

     try 
     { 
      client = listener.EndAcceptTcpClient(ar); 
     } 
     catch (SocketException ex) 
     { 
      //the client is corrupt 
      OnError(ex); 
     } 
     catch (ObjectDisposedException) 
     { 
      //Listener canceled 
      return; 
     } 

     //Get the next Client 
     listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), listener); 

     if (client == null) 
      return; //Abort if there was an error with the client 

     MyConnection connection = null; 
     try 
     { 
      //Client-Protocoll init 
      connection = Connect(client.GetStream()); 
     } 
     catch (Exception ex) 
     { 
      //The client is corrupt/invalid 
      OnError(ex); 

      client.Close(); 
     }    
    } 
0

這是一個簡單的例子,說明如何開始監聽,如何異步處理請求以及如何停止監聽。

完整的示例here

public class TcpServer 
{ 
    #region Public.  
    // Create new instance of TcpServer. 
    public TcpServer(string ip, int port) 
    { 
     _listener = new TcpListener(IPAddress.Parse(ip), port); 
    } 

    // Starts receiving incoming requests.  
    public void Start() 
    { 
     _listener.Start(); 
     _ct = _cts.Token; 
     _listener.BeginAcceptTcpClient(ProcessRequest, _listener); 
    } 

    // Stops receiving incoming requests. 
    public void Stop() 
    { 
     // If listening has been cancelled, simply go out from method. 
     if(_ct.IsCancellationRequested) 
     { 
      return; 
     } 

     // Cancels listening. 
     _cts.Cancel(); 

     // Waits a little, to guarantee 
     // that all operation receive information about cancellation. 
     Thread.Sleep(100); 
     _listener.Stop(); 
    } 
    #endregion 

    #region Private. 
    // Process single request. 
    private void ProcessRequest(IAsyncResult ar) 
    { 
     //Stop if operation was cancelled. 
     if(_ct.IsCancellationRequested) 
     { 
      return; 
     } 

     var listener = ar.AsyncState as TcpListener; 
     if(listener == null) 
     { 
      return; 
     } 

     // Check cancellation again. Stop if operation was cancelled. 
     if(_ct.IsCancellationRequested) 
     { 
      return; 
     } 

     // Starts waiting for the next request. 
     listener.BeginAcceptTcpClient(ProcessRequest, listener); 

     // Gets client and starts processing received request. 
     using(TcpClient client = listener.EndAcceptTcpClient(ar)) 
     { 
      var rp = new RequestProcessor(); 
      rp.Proccess(client); 
     } 
    } 
    #endregion 

    #region Fields. 
    private CancellationToken _ct; 
    private CancellationTokenSource _cts = new CancellationTokenSource(); 
    private TcpListener _listener; 
    #endregion 
}