簡體   English   中英

如何在主線程中使用 IcmpSendEcho2() 和 APC 回調?

[英]How to use IcmpSendEcho2() with APC callbacks in main thread?

首先, IcmpSendEcho2()的文檔自相矛盾:

它說:

如果IcmpSendEcho2或 Event 參數為 NULL,則同步調用 IcmpSendEcho2 function

然后它說:

當指定 ApcRoutine 或 Event 參數時,異步調用IcmpSendEcho2 function

我認為第一個應該是“如果 ApcRoutine AND Event 參數為 NULL”?

此外,它在返回值下說:

異步調用時,IcmpSendEcho2 function 返回 ERROR_IO_PENDING 表示操作正在進行中

但我沒有看到,我看到它返回 0 並且GetLastError()返回ERROR_IO_PENDING 那么,這兩種情況都存在,還是文檔完全錯誤?

現在進入下一個問題。 我想使用沒有事件的 ACP 回調異步使用IcmpSendEcho2() 這樣,如果要處理的主機數量非常多,我就不必擔心資源問題。 但是,它不起作用,因為沒有發生回調。 我在AcpRoutine參數下的文檔中找到了這一點:

當調用線程處於警報線程並且 ICMPv4 回復到達時調用的例程。

所以我相信我的問題是主線程不在可更改的 state 中。 由於我沒有要等待的事件,並且我不想等待超過完成所有操作所需的時間,我如何將主線程放在一個可變的 state 中,而不必猜測使用SleepEx()之類的東西? 另外,如果我確實使用了SleepEx(10,TRUE)之類的東西,所有的回調都會發生,還是你必須坐在一個循環中?

我的回調上下文結構包括一個共享的全局OutstandingCount類型變量,所以我知道所有請求何時完成。

ReplyBuffer也在上下文結構中。 在異步使用ReplyBuffer時隱藏在文檔中的另一個小塊是:

應用程序必須使用 IcmpParseReplies function 解析 ReplyBuffer 參數指向的數據

所以,這里的主要問題是:你應該如何正確使用IcmpSendEcho2() function 與AcpRoutine並且在主線程中沒有Event

- 更新 -

不確定我是否應該問一個全新的問題,但現在的問題是它不會為每個發送的IcmpSendEcho2Ex()調用ApcRoutine 以下代碼適用於我的普通網絡適配器(即 255.255.255.0),但適用於 255.255.0.0 網絡,因為outstandingcount永遠不會為零。

它掛在的適配器是:

VirtualBox Host-Only Ethernet Adapter DHCP Enable: Yes Autoconfiguration Enabled: Yes Autoconfiguration IPv4Address: 169.254.21.120 Subnet Mask: 255.255.0.0

還想知道像 10. 這樣的網絡需要多長時間,子網為 255.0.0.0。

這是以在 Win10 x64 上構建為 x64 的IPV4Scan()開頭的代碼:

#define PIO_APC_ROUTINE_DEFINED

#include <winternl.h>
#include <iphlpapi.h>
#include <IcmpAPI.h>

//--------------
// types
//--------------
typedef DWORD (WINAPI *LPFN_IcmpSendEcho2)(HANDLE, HANDLE , PIO_APC_ROUTINE, PVOID, IPAddr, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD);
typedef DWORD (WINAPI *LPFN_IcmpSendEcho2Ex)(HANDLE, HANDLE , PIO_APC_ROUTINE, PVOID, IPAddr, IPAddr, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD);
typedef HANDLE (WINAPI *LPFN_IcmpCreateFile)();
typedef BOOL (WINAPI *LPFN_IcmpCloseHandle)(HANDLE);
typedef DWORD (WINAPI *LPFN_IcmpParseReplies)(LPVOID, DWORD);

BYTE PingSignature[]={ 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8' };

typedef struct _sPingContext
{
  ULONG *OutstandingCount;                  // shared number of pings outstanding
  CMutex *Mutex;                            // mutex for ipsfound
  CNumericBuffer<uint32_t> *IPsFound;       // list of ips found (MSBF format)
  LPFN_IcmpParseReplies fnIcmpParseReplies; // function pointer 
  BYTE ReplyBuffer[sizeof(ICMP_ECHO_REPLY) + sizeof(PingSignature) + sizeof(IO_STATUS_BLOCK) + 8]; // reply buffer (see API docs)

  _sPingContext(ULONG *outstandingcount, CMutex *mutex, CNumericBuffer<uint32_t> *ipsfound, LPFN_IcmpParseReplies fnicmpparsereplies)
  {
    OutstandingCount=outstandingcount;
    Mutex=mutex;
    IPsFound=ipsfound;
    fnIcmpParseReplies=fnicmpparsereplies;
    memset(ReplyBuffer, 0, sizeof(ReplyBuffer));
  };
} sPingContext, *psPingContext;



//-------------------------------------------------------------------------
// Purpose:  Callback for async ping
//
// Input:    ioresult     - [i] io result of async operation
//           pingccontext - [i] context passed on ping
//           replysize    - [i] reply size of ReplyBuffer
//
// Output:   na
//
// Notes:    
//
VOID PingCallbackCommon(DWORD ioresult, sPingContext* pingcontext, DWORD replysize)
{
  // parse response buffer
  if (pingcontext) {
    if (ioresult==IP_SUCCESS) {
      if (pingcontext->fnIcmpParseReplies(pingcontext->ReplyBuffer, replysize)) {
        // point to reply buffer
        PICMP_ECHO_REPLY pechoreply=reinterpret_cast<PICMP_ECHO_REPLY>(pingcontext->ReplyBuffer);
        if (pechoreply->Status==IP_SUCCESS) {
          // check response
          if (pechoreply->DataSize==sizeof(PingSignature)) {
            if (memcmp(pechoreply->Data, PingSignature, pechoreply->DataSize)==0) {
              // successful ping
              pingcontext->Mutex->Lock();
              pingcontext->IPsFound->AddItem(pechoreply->Address);
              pingcontext->Mutex->Unlock();
            }
          }
        }
      }
    }
    // reduce count
    InterlockedDecrement(pingcontext->OutstandingCount);
    // clean up
    delete pingcontext;
  }
}

//-------------------------------------------------------------------------
// Purpose:  Callback for async ping
//
// Input:    apccontext - [i] context passed on ping
//
// Output:   na
//
// Notes:    
//
VOID PingCallbackOld(PVOID apcontext)
{
  sPingContext *pingcontext=reinterpret_cast<sPingContext*>(apcontext);
  PingCallbackCommon(IP_SUCCESS, pingcontext, sizeof(pingcontext->ReplyBuffer));
}

//-------------------------------------------------------------------------
// Purpose:  Callback for async ping
//
// Input:    apccontext - [i] context passed on ping
//           iostatusblock - [i] status of request
//
// Output:   na
//
// Notes:    
//
VOID PingCallback(PVOID apcontext, PIO_STATUS_BLOCK iostatusblock, ULONG reserved)
{
  PingCallbackCommon(iostatusblock->Status, reinterpret_cast<sPingContext*>(apcontext), iostatusblock->Information);
}


//-------------------------------------------------------------------------
// Purpose:  build list of network hosts using IPv4 Ping
//
// Input:    subnet    - [i] subnet being scanned (LSB format)
//           hoststart - [i] host starting number for scan
//           hostend   - [i] host ending number for scan
//           ips       - [io] numeric buffer to update with found addresses
//
// Output:   na
//
// Notes:    
//
void IPV4Ping(IPAddr sourceip, uint32_t subnet, uint32_t hoststart, uint32_t hostend, CNumericBuffer<uint32_t> &ips)
{
  // skip 127. network
  if ((sourceip & 0xFF)==127) 
    return;

  bool oldlib=false;

  LPFN_IcmpSendEcho2Ex fnIcmpSendEcho2Ex=NULL;
  LPFN_IcmpCreateFile fnIcmpCreateFile=NULL;
  LPFN_IcmpCloseHandle fnIcmpCloseHandle=NULL;
  LPFN_IcmpParseReplies fnIcmpParseReplies=NULL;

  // first thing is first - check which set of functions to use
  HMODULE hlib=LoadLibrary(_T("iphlpapi.dll"));
  if (hlib) {
    // load functions
    fnIcmpCreateFile=(LPFN_IcmpCreateFile) GetProcAddress(hlib, "IcmpCreateFile");
    fnIcmpSendEcho2Ex=(LPFN_IcmpSendEcho2Ex) GetProcAddress(hlib, "IcmpSendEcho2Ex");
    fnIcmpCloseHandle=(LPFN_IcmpCloseHandle) GetProcAddress(hlib, "IcmpCloseHandle");
    fnIcmpParseReplies=(LPFN_IcmpParseReplies) GetProcAddress(hlib, "IcmpParseReplies");
  }

  // check if have everything
  if (!hlib || fnIcmpCreateFile==NULL || fnIcmpSendEcho2Ex==NULL || fnIcmpCloseHandle==NULL || fnIcmpParseReplies==NULL) {
    // no, try old version
    oldlib=true;
    // clean up
    if (hlib) {
      FreeLibrary(hlib);
    }
    // load old lib
    hlib=LoadLibrary(_T("icmp.dll"));
    // check if loaded
    if (hlib) {
      // load functions
      fnIcmpCreateFile=(LPFN_IcmpCreateFile) GetProcAddress(hlib, "IcmpCreateFile");
      fnIcmpSendEcho2Ex=(LPFN_IcmpSendEcho2Ex) GetProcAddress(hlib, "IcmpSendEcho2Ex");
      fnIcmpCloseHandle=(LPFN_IcmpCloseHandle) GetProcAddress(hlib, "IcmpCloseHandle");
      fnIcmpParseReplies=(LPFN_IcmpParseReplies) GetProcAddress(hlib, "IcmpParseReplies");
    }
  }

  // check if have everything
  if (hlib) {
    if (fnIcmpCreateFile!=NULL && fnIcmpSendEcho2Ex!=NULL && fnIcmpCloseHandle!=NULL && fnIcmpParseReplies!=NULL) {
      // open icmp
      HANDLE hicmp=fnIcmpCreateFile();
      if (hicmp!=INVALID_HANDLE_VALUE) {
        // variables for callback handling
        ULONG outstandingcount=0;
        CMutex mutex;

        // process pings
        for (uint32_t host=hoststart; host<=hostend; host++) {
          // build full ip
          IPAddr ip=subnet | host;
          ip=GETMSBFDWORD(&ip);
          // create context 
          sPingContext *pcontext;
          if ((pcontext=new sPingContext(&outstandingcount, &mutex, &ips, fnIcmpParseReplies))!=NULL) {
            // count request
            InterlockedIncrement(&outstandingcount);
            // now issue ping
            DWORD result=fnIcmpSendEcho2Ex(hicmp, 
                                           NULL, 
                                           oldlib ? (PIO_APC_ROUTINE) PingCallbackOld : PingCallback, 
                                           pcontext, 
                                           sourceip, 
                                           ip, 
                                           PingSignature, 
                                           sizeof(PingSignature), 
                                           NULL, 
                                           pcontext->ReplyBuffer, 
                                           sizeof(pcontext->ReplyBuffer), 
                                           50);

            // check if failed
            if (result==0) {
              // check if because pending
              if (GetLastError()!=ERROR_IO_PENDING) {
                // no - use callback to clean up
                CDebugPrint::DebugPrint(_T("IcmpSendEcho Error %u\n"), GetLastError());
                PingCallbackOld(pcontext);
              }
              else {
                // fire off pending APC callbacks ready
                SleepEx(0, TRUE);
              }
            }
            else {
              // completed sync - use callback to clean up
              PingCallbackOld(pcontext);
            }
          }
        }

        // wait for completion
        while (outstandingcount) {
          // handle callbacks
          SleepEx(10, TRUE);
        }
        // clean up
        fnIcmpCloseHandle(hicmp);
      }
    }
    // clean up
    FreeLibrary(hlib);
  }
}

//-------------------------------------------------------------------------
// Purpose:  build list of network hosts by way of IP scan for V4
//
// Input:    ipadapteraddress  - [i] adapter ip address to build for
//
// Output:   na
//
// Notes:    ip addresses are MSBF
//
void IPV4Scan(IP_ADAPTER_UNICAST_ADDRESS *ipadapteraddress)
{
  // build the subnet mask to use
  if (ipadapteraddress->OnLinkPrefixLength<=32 && ipadapteraddress->OnLinkPrefixLength!=0) {
    in_addr ia=reinterpret_cast<sockaddr_in*>(ipadapteraddress->Address.lpSockaddr)->sin_addr;
    // valid mask length - build mask
    uint32_t rangemask=((1U<<(32-ipadapteraddress->OnLinkPrefixLength))-1);
    uint32_t mask=~rangemask;
    uint32_t subnet=GETMSBFDWORD(&ia.s_addr) & mask;
    CDebugPrint::DebugPrint(_T("Subnet %u.%u.%u.%u/%u\n"), (subnet>>24) & 0xFF, (subnet>>16) & 0xFF, (subnet>>8) & 0xFF, (subnet>>0) & 0xFF, ipadapteraddress->OnLinkPrefixLength);
    CDebugPrint::DebugPrint(_T("Scanning %u hosts\n"), (UINT32_MAX & rangemask)-1);

    CNumericBuffer<uint32_t> ipsfound;
    IPV4Ping(ia.s_addr, subnet, 1, (UINT32_MAX & rangemask)-1, ipsfound);

    for (UINT i=0; i<(UINT)ipsfound.GetCount(); i++) {
      uint32_t ip=ipsfound[i];
      CDebugPrint::DebugPrint(_T("Ping found %u.%u.%u.%u\n"), ip & 0xFF, (ip>>8) & 0xFF, (ip>>16) & 0xFF, (ip>>24) & 0xFF);
    }
  }
  else CDebugPrint::DebugPrint(_T("Invalid subnet length %u\n"), ipadapteraddress->OnLinkPrefixLength);
}

我認為第一個應該是“如果 ApcRoutine AND Event 參數為 NULL”?

是的,你說得對。

但我沒有看到,我看到它返回 0 並且 GetLastError() 返回 ERROR_IO_PENDING。 那么,這兩種情況都存在,還是文檔完全錯誤?

文檔完全錯誤。 事實上IcmpSendEcho2[Ex]通過SetLastError返回BOOL和錯誤代碼(更確切地說是RtlNtStatusToDosError

所以在異步調用它返回FALSE (0) 並且GetLastError()將是ERROR_IO_PENDING如果一切正常 - 這意味着將調用 apc 回調,或者如果失敗則另一個錯誤 - apc 回調將不會被調用(在這種情況下最好自己調用它, 用於常見的錯誤處理)

如何將主線程放入可變的 state

這已經取決於您的線程在做什么。 在某些情況下,可能使用MsgWaitForMultipleObjectsEx function 編寫事件循環 - 立即等待 windows 事件並發出警報。 也可以等待一些對象。 如果您無法使用MsgWaitForMultipleObjectsEx重寫自我消息循環 - 您可以從工作線程調用,或定期調用SleepEx(0, TRUE)或未記錄NtTestAlert 不知道你的主線程在做什么——很難確切地說什么更好。

演示代碼可能如下所示:

#include <iphlpapi.h>
#include <IPExport.h>
#include <icmpapi.h>

class EchoRequestContext
{
    HANDLE _hFile = 0;
    PVOID _ReplyBuffer = 0;
    LONG _dwRefCount = 1;
    ULONG _dwThreadId = GetCurrentThreadId();

    static void WINAPI sOnApc(PVOID This, PIO_STATUS_BLOCK piosb, ULONG )
    {
        reinterpret_cast<EchoRequestContext*>(This)->OnApc(
            RtlNtStatusToDosError(piosb->Status), 
            (ULONG)piosb->Information);
    }

    void OnApc(ULONG dwError, ULONG ReplySize)
    {
        OnReply(dwError, (PICMP_ECHO_REPLY)_ReplyBuffer, ReplySize);
        if (_ReplyBuffer) delete [] _ReplyBuffer;
        Release();
    }

    void OnReply(ULONG dwError, PICMP_ECHO_REPLY ReplyBuffer, ULONG ReplySize)
    {
        if (dwError)
        {
            DbgPrint("dwError=%u\n", dwError);
            return ;
        }
        
        if (IcmpParseReplies(ReplyBuffer, ReplySize))
        {
            __nop();
        }
    }

    ~EchoRequestContext()
    {
        if (_hFile) IcmpCloseHandle(_hFile);
        PostThreadMessageW(_dwThreadId, WM_QUIT, 0, 0);
    }
public:

    void AddRef()
    {
        InterlockedIncrementNoFence(&_dwRefCount);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRefCount))
        {
            delete this;
        }
    }

    ULONG Create()
    {
        HANDLE hFile = IcmpCreateFile();

        if (hFile == INVALID_HANDLE_VALUE) 
        {
            return GetLastError();
        }

        _hFile = hFile;

        return NOERROR;
    }

    void SendEcho(
        IPAddr DestinationAddress, 
        const void* RequestData, 
        WORD RequestSize,
        ULONG ReplySize, 
        ULONG Timeout, 
        UCHAR Flags, 
        UCHAR Ttl)
    {
        if (PVOID ReplyBuffer = new UCHAR[ReplySize])
        {
            _ReplyBuffer = ReplyBuffer;

            IP_OPTION_INFORMATION opt = { Ttl, 0, Flags };

            AddRef();

            ULONG dwError = IcmpSendEcho2Ex(_hFile, 0, sOnApc, this, 
                0, DestinationAddress, 
                const_cast<void*>(RequestData), RequestSize, 
                &opt, ReplyBuffer, ReplySize, Timeout) ? NOERROR : GetLastError();

            switch (dwError)
            {
            case NOERROR:
            case ERROR_IO_PENDING:
                break;
            default:
                OnApc(dwError, 0 );
            }
            return ;
        }

        OnApc(ERROR_OUTOFMEMORY, 0);
    }
};

#define IP(a, b, c, d) ((ULONG)(a + (b << 8) + (c << 16) + (d << 24)))

void EchoTest()
{
    WSADATA wd;
    if (NOERROR == WSAStartup(WINSOCK_VERSION, &wd))
    {
        if (EchoRequestContext* p = new EchoRequestContext)
        {
            if (p->Create() == NOERROR)
            {
                p->SendEcho(IP(8,8,8,8), "1234567890ABCDEF", 16, 0x100, 4000, IP_FLAG_DF, 255);
            }

            p->Release();
        }

        MSG msg;
__loop:
        switch (MsgWaitForMultipleObjectsEx(0, 0, INFINITE, 
                QS_ALLINPUT, MWMO_ALERTABLE|MWMO_WAITALL))
        {
        default:
            __debugbreak();
            break;
        case WAIT_FAILED:
            break;

        case WAIT_OBJECT_0:
            while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
            {
                if (msg.message == WM_QUIT)
                {
                    goto __exit;
                }
            }

        case STATUS_USER_APC: // == WAIT_IO_COMPLETION
            goto __loop;
        }
__exit:

        WSACleanup();
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM