2010-11-04 63 views
0

當一款軟件按照您的想法運行時,它總是令人滿意,但只要我不明白爲什麼(或如何)運行,我認爲這是一個大問題。我做了一個ICMP監聽器來彌補'UdpClient'類中的一個缺陷,即當遠程主機意外地變得不可用時,不會返回由請求產生的適當的ICMP消息。 (ICMP類型3,任何代碼)。它只是拋出一個錯誤:(WSACONNRESET)和'無法訪問已處理的對象',而不是使用ICMP代碼進行回覆。ICMP監聽器問題

我現在運行的代碼現在使用ManualResetEvents作爲信號,這是可以接受的。結果數據經過仔細檢查,即使是在時隙和序列號級別,一切都很好。我只是不明白爲什麼每次迭代循環都需要一個新的'StateObject'。 我沒有理由說明當前的緩衝區不能被重用。不過,如果每次迭代都不使用新的緩衝區,則返回的緩衝區無效(儘管如此)。然後緩衝區引用從我的主機到外部目標的數據包,而不是來自遠程主機的緩衝區。因此,我看到我的系統回覆了一個迴應(類型0)請求,而不是實際的迴應請求(類型8)(如Wireshark所示)。只要我改變循環來使用新的'StateObject',一切都很好。微軟的例子並沒有包括一次性'StateObject',所以我爲此創建了一個從IDisposable繼承的新類。

此外,當從ManualResetEvent信令切換到'AsyncWaitHandle'信號時,該過程僅在對回調委託使用遞歸調用時起作用。如果我不這樣做,'IASyncResult''IsCompleted'並不總是被設置(即使所有的緩衝區都是相同的60個字節),所以它將無限期地等待句柄。

一個很長的故事(和很多代碼),但我希望有人能夠解釋這些問題。

using System; 
using System.Collections.Generic; 
using System.Net; 
using System.Net.Sockets; 
using System.Linq; 
using System.Text; 
using System.Threading; 
using log4net; 

namespace ICMPTest 
{ 
    public class ICMPCheck 
    { 
     private static ManualResetEvent gotMessage; 
     private static IPAddress ipAddress; 
     private static IntPtr stateHandle; // Dont know what it is for, or what to do with it 
     private static Disposables.StateObject so = null; 
     private static Socket icmpClient; 
     private static EndPoint remoteRawEndPoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0); 
     public static Queue<byte[]> m_icmpQueue = new Queue<byte[]>(); 
     public static object m_syncLock = new object(); 
     private static IPEndPoint NIC = null; 
     private static int Queued = 0; 
     private static int DeQueued = 0; 
     private static int CallCount = 0; 
     public static IAsyncResult iar; 

     public static void Start() 
     { 
      try 
      { 
       using (icmpClient = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) 
       { 
        IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName()); 
        IPHostEntry hostInfo = Dns.Resolve(Dns.GetHostName()); 
        IPAddress[] address = hostInfo.AddressList; 
        ipAddress = address[0]; 
        NIC = new IPEndPoint(ipAddress, 0); 
        icmpClient.Bind(NIC); // Bind to localhost, port any 
        byte[] inBytes = new byte[] { 1, 0, 0, 0 }; 
        byte[] outBytes = new byte[] { 0, 0, 0, 0 }; 
        icmpClient.IOControl(IOControlCode.ReceiveAll, inBytes, outBytes); //only outgoing packets 
        icmpClient.ReceiveBufferSize = 1024; 

        while (true) 
        { 
         //gotMessage = new ManualResetEvent(false); 
         using (so = new Disposables.StateObject(stateHandle)) 
         { 

          so.workSocket = icmpClient; 

          iar = icmpClient.BeginReceiveFrom(so.buffer, 0, Disposables.StateObject.BUFFER_SIZE, 0, ref remoteRawEndPoint, new AsyncCallback(ReceiveFromCallback), so); //blocking 

          iar.AsyncWaitHandle.WaitOne(); //gotMessage.WaitOne(); //iar.AsyncWaitHandle.WaitOne(); // seems to be unreliable 

          for (int i = DeQueued; i < Queued; i++) 
          { 
           //DequeueParse.DequeueAndParse(ref so); 
           Interlocked.Increment(ref DeQueued); 
           ICMPProgram.logger.Debug("ICMPCheck-0: Signal + Message received: " + remoteRawEndPoint.ToString() + " Queue: " + m_icmpQueue.Count.ToString() + " " + Queued.ToString() + " " + DeQueued.ToString()); 
          } 
         } // using StateObject 
         //gotMessage.Dispose(); 
        }// while 
       }//using Socket 
      } // try 
      catch (Exception excp) 
      { 
       ICMPProgram.logger.Error("ICMPCheck: Exception Mainblock. " + excp.Message); 
      } 
      return; 
     } 

     private static void ReceiveFromCallback(IAsyncResult iar) 
     { 
      Interlocked.Increment(ref CallCount); 
      try 
      { 
       if (ICMPProgram.stopRequest) return; 
       Disposables.StateObject state = (Disposables.StateObject)iar.AsyncState; 
       Socket client = ((Disposables.StateObject)iar.AsyncState).workSocket; 
       EndPoint tempRemoteEP = (EndPoint)new IPEndPoint(IPAddress.Any, 0); 

       int bytesRead = client.EndReceiveFrom(iar, ref tempRemoteEP); 

       if (bytesRead > 0) 
       { 
        if (!(((IPEndPoint)tempRemoteEP).Address).Equals(NIC.Address)) // ignore messages from local host 
        { 
         byte[] _icmpData = new byte[bytesRead]; 
         byte[] icmpType = new byte[1]; 
         Buffer.BlockCopy(state.buffer, 20, icmpType, 0, 1); 

         //if (((int)icmpType[0] == 3)) // only type 3 
         if (true) // all tyoes for now 
         { 
          Buffer.BlockCopy(state.buffer, 0, _icmpData, 0, bytesRead); 
          lock (m_syncLock) 
          { 
           m_icmpQueue.Enqueue(_icmpData); 
           Interlocked.Increment(ref Queued); 
          } 
         } 
         // the next callback is required when using AsyncWaitHandle.WaitOne signaling, not required (but useful for high volume) for ManualResetEvents 
         client.BeginReceiveFrom(state.buffer, 0, Disposables.StateObject.BUFFER_SIZE, 0, ref tempRemoteEP, new AsyncCallback(ReceiveFromCallback), state); // suitable for high volume 
         remoteRawEndPoint = tempRemoteEP; 
         //ICMPProgram.logger.Debug("ICMPCheck: Bytes: " + bytesRead.ToString() + ", Type: " + icmpType[0].ToString() + " " + tempRemoteEP.ToString() + " " + m_icmpQueue.Count.ToString() + " " + Queued.ToString() + " " + CallCount.ToString() + " " + iar.IsCompleted.ToString()); 
        } 
       } 
       else 
       { 
        ICMPProgram.logger.Debug("ICMPCheck: bytesRead = 0 "); 
       } 
      } 
      catch (Exception excp) 
      { 
       ICMPProgram.logger.Debug("ICMPCheck:ReceiveFromCallback main " + excp.Message); 
      } 
      finally 
      { 
       //gotMessage.Set(); 
      } 
     } 
    } 
} 

回答

0
iar = icmpClient.BeginReceiveFrom(...); 
iar.AsyncWaitHandle.WaitOne(); 

有使用BeingReceiveFrom(),然後等待異步操作完成是沒有意義的。只需使用ReceiveFrom()。現在你不再需要'StateObject'來讓回調知道響應屬於什麼請求。

+0

剛剛完成;奇蹟般有效。 (並用很少的代碼!)謝謝。 – Nebbukadnezzar 2010-11-04 15:26:25