[英]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.