异步模式下使用的WinHttp-ERROR_INTERNET_CANNOT_CONNECT如何干净地关闭连接

WinHttp used in async mode - ERROR_INTERNET_CANNOT_CONNECT how to cleanly close connection

本文关键字:CONNECT 何干净 连接 CANNOT INTERNET 模式 WinHttp-ERROR 异步      更新时间:2023-10-16

在请求的回调过程中,我得到了很多ERROR_INTERNET_CANNOT_CONNECT(12029代码)。我在异步模式下(在服务器上)使用WinHttp。在这种情况下,如何干净地关闭连接。你只是使用这样的东西吗(就像你通常关闭连接一样?):

            ::WinHttpSetStatusCallback(handle, NULL, 0, 0);
            ::WinHttpCloseHandle(this->handle));

我问这个问题是因为我有一些与winhttp dll相关的奇怪内存泄漏,这种情况发生在所描述的情况下(想要创建数百个可能被公司内部防火墙阻止的并发连接,或者目标服务器丢弃连接)。我已经看过msdn上的WinHttpCloseHandle文档。。。

以下是我如何处理回调状态:

    template <typename T>
void WinHttp::AsyncRequest<T>::OnCallback(DWORD code, const void* info, DWORD length)
{
    T* pT = static_cast<T*>(this);
    switch (code)
    {
    case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
    case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
        {
            HRESULT result = pT->OnWriteData();
            if (FAILED(result))
            {
                throw CommunicationException(::GetLastError());
            }
            if (S_FALSE == result)
            {
                if (!::WinHttpReceiveResponse(handle, 0)) // reserved
                {
                    throw CommunicationException(::GetLastError());
                }
            }
            break;
        }
    case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
        {
            DWORD statusCode;
            DWORD statusCodeSize = sizeof(DWORD);
            if (!::WinHttpQueryHeaders(handle, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX))
            {
                throw CommunicationException(::GetLastError());
            }
            pT->OnStatusCodeReceived(statusCode);
            if (!::WinHttpQueryDataAvailable(handle, 0))
            {
                // If a synchronous error occured, throw error.  Otherwise
                // the query is successful or asynchronous.
                throw CommunicationException(::GetLastError());
            }
            break;
        }
    case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
        {
            unsigned int size = *((LPDWORD) info);
            if (size == 0)
            {
                pT->OnResponseComplete(S_OK);
            }
            else
            {
                unsigned int sizeToRead = (size <= chunkSize) ? size : chunkSize;
                if (!::WinHttpReadData(handle, &buffer[0], sizeToRead, 0)) // async result
                {
                    throw CommunicationException(::GetLastError());
                }
            }
            break;
        }
    case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
        {
            if (length != 0)
            {
                pT->OnReadComplete(&buffer[0], length);
                if (!::WinHttpQueryDataAvailable(handle, 0))
                {
                    // If a synchronous error occured, throw error.  Otherwise
                    // the query is successful or asynchronous.
                    throw CommunicationException(::GetLastError());
                }
            }
            else
            {
                pT->OnResponseComplete(S_OK);
            }
            break;
        }
    case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
        {
            {
                throw CommunicationException(::GetLastError());
            }
            break;
        }
    }
}

这里的缓冲区是一个向量,一旦请求启动,它就保留了8K。提前谢谢。

在OnResponseComplete中,我最终也会调用OnResponsEerror:

::WinHttpSetStatusCallback(handle, NULL, 0, 0);
 assert(::WinHttpCloseHandle(this->handle));
 this->handle = nullptr;

Roman R对这个问题是正确的,只是想在响应中包含更多细节。取消很棘手。对于WinHTTP,您需要记住回调是在线程池上触发的。它们可以在随机线程上到达,由于线程池不能保证何时执行回调,您可以调用WinHttpSetStatus callback来清除回调,然后稍后仍然接收回调。因此,您需要自己同步。一个更微妙的问题是,您无法从回调中回调回WinHTTP。处理WinHTTP回调最安全、最可靠的方法是将回调调度到单个线程。这可以是一个UI线程或一个单线程线程池。然后,您还需要先关闭句柄,然后等待回调来指示句柄已关闭(WINHTTP_callback_STATUSHANDLE_CLOSING)——只有这样,才可以安全地释放任何特定于连接的资源、回调等。

到目前为止,我的测试表明,关闭请求句柄必须在回调之外完成,也必须在连接句柄的最终关闭之外完成。可以使用QueueUserApc在单独的线程上对关闭请求的意图以及随后的连接关闭和其他清理进行排队,以便至少同步这两个操作。

编辑问题仍然存在。我在下面写的并没有解决这个问题。

所描述的问题实际上与ERROR_CANNOT_CONNECT没有太大关系,而是与我"不正确"处理winhttp的异步状态回调通知的方式有关。如果有人感兴趣,我会在这里复制一种处理状态通知的典型方法。