[英]How to use IcmpSendEcho2() with APC callbacks in main thread?
First, the documentation for IcmpSendEcho2()
contradicts itself:首先,
IcmpSendEcho2()
的文档自相矛盾:
It says:它说:
The
IcmpSendEcho2
function is called synchronously if the ApcRoutine or Event parameters are NULL如果
IcmpSendEcho2
或 Event 参数为 NULL,则同步调用 IcmpSendEcho2 function
Then it says:然后它说:
The
IcmpSendEcho2
function is called asynchronously when either the ApcRoutine or Event parameters are specified当指定 ApcRoutine 或 Event 参数时,异步调用
IcmpSendEcho2
function
I presume the first one should be "if the ApcRoutine AND Event paramters are NULL"?我认为第一个应该是“如果 ApcRoutine AND Event 参数为 NULL”?
Also, it says under the return value:此外,它在返回值下说:
When called asynchronously, the IcmpSendEcho2 function returns ERROR_IO_PENDING to indicate the operation is in progress
异步调用时,IcmpSendEcho2 function 返回 ERROR_IO_PENDING 表示操作正在进行中
But I don't see that, I see it return 0 and GetLastError()
returns ERROR_IO_PENDING
.但我没有看到,我看到它返回 0 并且
GetLastError()
返回ERROR_IO_PENDING
。 So, can both cases exist, or is the documentation completely wrong?那么,这两种情况都存在,还是文档完全错误?
Now on to the next issue.现在进入下一个问题。 I wanted to use
IcmpSendEcho2()
asynchronously using the ACP callback without events.我想使用没有事件的 ACP 回调异步使用
IcmpSendEcho2()
。 This way, I didn't have to worry about resources should the number of hosts to process be extremely large.这样,如果要处理的主机数量非常多,我就不必担心资源问题。 However, it doesn't work because no callback occurs.
但是,它不起作用,因为没有发生回调。 I found this in the documentation under the
AcpRoutine
parameter:我在
AcpRoutine
参数下的文档中找到了这一点:
The routine that is called when the calling thread is in an alertable thread and an ICMPv4 reply arrives.
当调用线程处于警报线程并且 ICMPv4 回复到达时调用的例程。
So I believe my problem is the main thread is not in an alterable state.所以我相信我的问题是主线程不在可更改的 state 中。 Since I don't have an event to wait on, and I don't want to wait beyond the time it takes to complete everything, how do I put the main thread in an alterable state without having to guess using something like
SleepEx()
?由于我没有要等待的事件,并且我不想等待超过完成所有操作所需的时间,我如何将主线程放在一个可变的 state 中,而不必猜测使用
SleepEx()
之类的东西? Also, if I did use something like SleepEx(10,TRUE)
, would all the callbacks occur, or do you have to sit in a loop?另外,如果我确实使用了
SleepEx(10,TRUE)
之类的东西,所有的回调都会发生,还是你必须坐在一个循环中?
My callback context structure includes a shared global OutstandingCount
type variable so I'd know when all requests were completed.我的回调上下文结构包括一个共享的全局
OutstandingCount
类型变量,所以我知道所有请求何时完成。
Also the ReplyBuffer
is in the context structure. ReplyBuffer
也在上下文结构中。 Another little nugget hidden in the documentation regarding the ReplyBuffer
when using it asynchronously is:在异步使用
ReplyBuffer
时隐藏在文档中的另一个小块是:
The application must parse the data pointed to by ReplyBuffer parameter using the IcmpParseReplies function
应用程序必须使用 IcmpParseReplies function 解析 ReplyBuffer 参数指向的数据
So, the main question here: How are you supposed to properly use the IcmpSendEcho2()
function with a AcpRoutine
and no Event
in a main thread?所以,这里的主要问题是:你应该如何正确使用
IcmpSendEcho2()
function 与AcpRoutine
并且在主线程中没有Event
?
-- Update -- - 更新 -
Not sure if I should ask an entirely new question but now a problem where it doesn't call the ApcRoutine
for every IcmpSendEcho2Ex()
sent.不确定我是否应该问一个全新的问题,但现在的问题是它不会为每个发送的
IcmpSendEcho2Ex()
调用ApcRoutine
。 The following code works for my normal network adapters (which are 255.255.255.0) but hangs for a 255.255.0.0 network because the outstandingcount
never gets to zero.以下代码适用于我的普通网络适配器(即 255.255.255.0),但适用于 255.255.0.0 网络,因为
outstandingcount
永远不会为零。
The adapter it hangs on is:它挂在的适配器是:
VirtualBox Host-Only Ethernet Adapter DHCP Enable: Yes Autoconfiguration Enabled: Yes Autoconfiguration IPv4Address: 169.254.21.120 Subnet Mask: 255.255.0.0 VirtualBox Host-Only Ethernet Adapter DHCP Enable: Yes Autoconfiguration Enabled: Yes Autoconfiguration IPv4Address: 169.254.21.120 Subnet Mask: 255.255.0.0
Also wonder how long it would take on networks like 10. with a subnet of 255.0.0.0.还想知道像 10. 这样的网络需要多长时间,子网为 255.0.0.0。
Here's the code that starts with the IPV4Scan()
built as x64 on Win10 x64:这是以在 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);
}
I presume the first one should be "if the ApcRoutine AND Event paramters are NULL"?
我认为第一个应该是“如果 ApcRoutine AND Event 参数为 NULL”?
yes, you correct.是的,你说得对。
But I don't see that, I see it return 0 and GetLastError() returns ERROR_IO_PENDING.
但我没有看到,我看到它返回 0 并且 GetLastError() 返回 ERROR_IO_PENDING。 So, can both cases exist, or is the documentation completely wrong?
那么,这两种情况都存在,还是文档完全错误?
documentation completely wrong.文档完全错误。 by fact
IcmpSendEcho2[Ex]
return BOOL
and error code via SetLastError
( more exactly by RtlNtStatusToDosError
)事实上
IcmpSendEcho2[Ex]
通过SetLastError
返回BOOL
和错误代码(更确切地说是RtlNtStatusToDosError
)
so on asynchronous call it return FALSE (0) and GetLastError()
will be ERROR_IO_PENDING
if all ok - this mean apc callback will be called, or another error if fail - apc callback will be not called (better call it by self in this case, for common error handling)所以在异步调用它返回FALSE (0) 并且
GetLastError()
将是ERROR_IO_PENDING
如果一切正常 - 这意味着将调用 apc 回调,或者如果失败则另一个错误 - apc 回调将不会被调用(在这种情况下最好自己调用它, 用于常见的错误处理)
how do I put the main thread in an alterable state
如何将主线程放入可变的 state
this already depend from what your thread doing.这已经取决于您的线程在做什么。 in some case possible write event loop with
MsgWaitForMultipleObjectsEx
function - at once wait on windows events and be alertable.在某些情况下,可能使用
MsgWaitForMultipleObjectsEx
function 编写事件循环 - 立即等待 windows 事件并发出警报。 also possible wait on some objects too.也可以等待一些对象。 if you can not rewrite self message loop with
MsgWaitForMultipleObjectsEx
- you can do call from worked thread, or periodically call SleepEx(0, TRUE)
or undocumented NtTestAlert
.如果您无法使用
MsgWaitForMultipleObjectsEx
重写自我消息循环 - 您可以从工作线程调用,或定期调用SleepEx(0, TRUE)
或未记录NtTestAlert
。 without know what your main thread doing - hard say exactly what is better.不知道你的主线程在做什么——很难确切地说什么更好。
demo code can look like:演示代码可能如下所示:
#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.