val*_*ldo 5 c c++ windows networking wininet
对不起这么长的问题.我只是花了好几天试图解决我的问题,而且我已经筋疲力尽了.
我正在尝试以异步模式使用WinINet.我必须说......这简直就是疯了.我真的无法理解这一点.它做了很多事情,但遗憾的是它的异步API设计得非常糟糕,以至于它不能用于具有高稳定性要求的严肃应用程序.
我的问题如下:我需要连续进行大量的HTTP/HTTPS事务,而我还需要能够在请求时立即中止它们.
我将按照以下方式使用WinINet:
InternetOpen带有INTERNET_FLAG_ASYNC标志的函数初始化WInINet用法.InternetSetStatusCallback).现在,为了执行我想做的事务:
InternetOpenUrl发起交易.在异步模式下,它通常会立即返回错误,即ERROR_IO_PENDING.其中一个参数是'context',它将传递给回调函数.我们将其设置为指向每个事务状态结构的指针.INTERNET_STATUS_HANDLE_CREATED.此时我们保存WinINet会话句柄.INTERNET_STATUS_REQUEST_COMPLETE在事务完成时调用回调函数.这允许我们使用一些通知机制(例如设置事件)来通知原始线程事务已完成.InternetCloseHandle),并删除状态结构.到目前为止似乎没有问题.
如何中止正在执行的事务?一种方法是关闭相应的WinINet句柄.而且由于WinINet没有这样的功能InternetAbortXXXX- 关闭句柄似乎是唯一的中止方式.
确实这很有效.这样的事务立即完成ERROR_INTERNET_OPERATION_CANCELLED错误代码.但是这里所有的问题都开始了......
我所遇到的第一个令人不快的意外的是,往往的WinINet有时调用回调函数,即使是交易后,它已被中止.根据MSDN,它INTERNET_STATUS_HANDLE_CLOSING是回调函数的最后一次调用.但这是谎言.我看到的是,有时会有INTERNET_STATUS_REQUEST_COMPLETE相同句柄的后续通知.
我还尝试在关闭之前禁用事务句柄的回调函数,但这没有帮助.似乎WinINet的回调调用机制是异步的.因此 - 它甚至可以在事务句柄关闭后调用回调函数.
这就产生了一个问题:只要WinINet 可以调用回调函数 - 显然我无法释放事务状态结构.但是我怎么知道WinINet是否会这么称呼它呢?从我看到的 - 没有一致性.
不过我已经解决了这个问题.相反,我现在保留一个分配的事务结构的全局映射(当然是受关键部分保护).然后,在回调函数内部,我确保事务确实存在并在回调调用期间锁定它.
但后来我发现了另一个问题,到目前为止我无法解决.当我在交易开始后不久中止交易时就会出现这种情况.
发生的是我调用InternetOpenUrl,它返回ERROR_IO_PENDING错误代码.然后我只是等待(通常很短),直到INTERNET_STATUS_HANDLE_CREATED通知调用回调函数.然后 - 保存事务句柄,现在我们有机会在没有句柄/资源泄漏的情况下中止,我们可以继续.
在这一刻之后,我试图完全中止.也就是说,收到后立即关闭此手柄.猜猜会发生什么?WinINet崩溃,内存访问无效!这与我在回调函数中做的任何事情无关.甚至没有调用回调函数,崩溃在WinINet内部的某个地方.
另一方面,如果我等待下一个通知(例如'解析名称') - 通常它会起作用.但有时崩溃也是如此!如果我Sleep在获得句柄和关闭它之间放一些最小的问题似乎消失了.但显然这不是一个严肃的解决方案.
所有这些让我得出结论:WinINet的设计很差.
我错了吗?这是我不明白的事吗?或者WinINet不能安全使用?
编辑:
这是演示第二个问题的最小代码块:崩溃.我删除了所有错误处理等.
HINTERNET g_hINetGlobal;
struct Context
{
HINTERNET m_hSession;
HANDLE m_hEvent;
};
void CALLBACK INetCallback(HINTERNET hInternet, DWORD_PTR dwCtx, DWORD dwStatus, PVOID pInfo, DWORD dwInfo)
{
if (INTERNET_STATUS_HANDLE_CREATED == dwStatus)
{
Context* pCtx = (Context*) dwCtx;
ASSERT(pCtx && !pCtx->m_hSession);
INTERNET_ASYNC_RESULT* pRes = (INTERNET_ASYNC_RESULT*) pInfo;
ASSERT(pRes);
pCtx->m_hSession = (HINTERNET) pRes->dwResult;
VERIFY(SetEvent(pCtx->m_hEvent));
}
}
void FlirtWInet()
{
g_hINetGlobal = InternetOpen(NULL, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_ASYNC);
ASSERT(g_hINetGlobal);
InternetSetStatusCallback(g_hINetGlobal, INetCallback);
for (int i = 0; i < 100; i++)
{
Context ctx;
ctx.m_hSession = NULL;
VERIFY(ctx.m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL));
HINTERNET hSession = InternetOpenUrl(
g_hINetGlobal,
_T("http://ww.google.com"),
NULL, 0,
INTERNET_FLAG_NO_UI | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD,
DWORD_PTR(&ctx));
if (hSession)
ctx.m_hSession = hSession;
else
{
ASSERT(ERROR_IO_PENDING == GetLastError());
WaitForSingleObject(ctx.m_hEvent, INFINITE);
ASSERT(ctx.m_hSession);
}
VERIFY(InternetCloseHandle(ctx.m_hSession));
VERIFY(CloseHandle(ctx.m_hEvent));
}
VERIFY(InternetCloseHandle(g_hINetGlobal));
}
Run Code Online (Sandbox Code Playgroud)
通常在第一次/第二次迭代时,应用程序崩溃.WinINet创建的线程之一会生成访问冲突:
Access violation reading location 0xfeeefeee.
Run Code Online (Sandbox Code Playgroud)
值得注意的是,上述地址对用C++编写的代码(至少是MSVC)有特殊意义.当您删除具有vtable(即具有虚函数)的对象时AFAIK - 它被设置为上述地址.因此,它试图调用已删除对象的虚函数.
小智 7
Context ctx的声明是问题的根源,它在for(;;)循环中声明,因此它是为每个循环创建的局部变量,它将被销毁,并且在每个循环结束时不再可访问.
因此,当调用回调时,ctx已经被销毁,指针被传递给回调点指向被破坏的ctx,无效的内存指针会导致崩溃.
特别感谢卢克。
InternetConnect当我明确使用+ HttpOpenRequest+HttpSendRequest而不是 all-in-one时,所有问题都消失了InternetOpenUrl。
我没有收到有关请求句柄的任何通知(不要与“连接”句柄混淆)。而且不再发生崩溃。