Скачать HTTP с динамическим перераспределением и повторов


Этот код предназначен для загрузки файлов через службы WinHTTP Либ, это вызывается StartDownload функция, которая пытается скачать N раз, прежде чем возвращать false, если максимум превышен разрешенный лимит. Я беспокоюсь о динамическом realloc участие в Download функция, это мой подход здесь имеют какие-либо недостатки, или есть какие-то улучшения, что можно сделать?

#include "HttpDownload.h"
#include <Windows.h>
#include <Winhttp.h>
#pragma comment(lib, "winhttp.lib")

bool bResults = FALSE;
HINTERNET hSession = 0, hConnect = 0, hRequest = 0;
int RepeatedTimes = 0;

void HttpDownload::CloseHandles() {
    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);
}

bool HttpDownload::InitHttp() {
    if (!(hSession = WinHttpOpen(0, 0, 0, 0, 0))) return false;
    if (!(hConnect = WinHttpConnect(hSession, L"127.0.0.1", 80, 0))) return false;
    if (!(hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/file.ext", 0, 0, 0, 0))) return false;
    if (!(bResults = WinHttpSendRequest(hRequest, 0, 0, 0, 0, 0, 0))) return false;
    if (!(bResults = WinHttpReceiveResponse(hRequest, 0))) return false;
    return true;
}

bool HttpDownload::Download() {
    if (!bResults) return false;
    unsigned long dwSize = 0;
    unsigned long dwDownloaded = 0;
    void *buf;
    bool failed = false;
    int curalloc = 8192;
    unsigned char *jar = (unsigned char*)malloc(curalloc);
    int totalDownloaded = 0;
    #define failbreak failed = true; break;
    while (1) {
        dwSize = 0;
        if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { failbreak }
        if (dwSize == 0) break;
        buf = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_READWRITE);
        if (!buf) { failbreak }
        else {
            if (!WinHttpReadData(hRequest, buf, dwSize, &dwDownloaded)) { failbreak }
            else {
                realloc:
                if ((totalDownloaded + dwDownloaded) > curalloc) {
                    unsigned char *m = (unsigned char*)malloc(curalloc * 2);
                    curalloc = curalloc * 2;
                    memcpy(m, jar, totalDownloaded);
                    free(jar);
                    jar = m;
                    goto realloc;
                }
                memcpy((jar + totalDownloaded), buf, dwDownloaded);
                totalDownloaded += dwDownloaded;
            }
            VirtualFree(buf, 0, MEM_RELEASE);
        }
    } if (failed) return false;
    CloseHandles();
    FinalResult = (unsigned char*)malloc(totalDownloaded);
    memcpy(FinalResult, jar, totalDownloaded);
    free(jar);
    FinalDownloadedSize = totalDownloaded;
    return true;
}

bool HttpDownload::StartDownload() {
    CloseHandles();
    if (RepeatedTimes > 4) { return false; }
    RepeatedTimes++;
    bool r = InitHttp();
    if (!r) StartDownload();
    r = Download();
    if (!r) StartDownload();
    return true;
}


Комментарии
1 ответ

сначала StartDownload использовать рекурсию, когда она абсолютна не нужна здесь. достаточно просто петли, как -

ULONG nTry = 4, dwError;
do {
dwError = Download();
} while(dwError && --Try);

далее - вам не нужно заново все ручками - hSession, hConnect, hRequest отправить запрос не удается, но только hRequest. в hSession мы обычно должны открыть только один раз. в hConnect - раз в URL. обратите внимание, что WinHttpConnect на самом деле не подключиться к серверу. это просто запомнить URL-адрес сервера и порт во внутренних структурах. если WinHttpOpen или WinHttpConnect - нет смысла пытаться вызвать его снова (это оффлайн звонки)

поэтому код синхронно (что плохо) обработки должен выглядеть так:

void test()
{
if (HINTERNET hSession = WinHttpOpen( 0,
WINHTTP_ACCESS_TYPE_NO_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0))
{
if (HINTERNET hConnect = WinHttpConnect(hSession, L"stackoverflow.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
{
int nTry = 4;
ULONG dwError;
do
{
if (HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", NULL,
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_REFRESH|WINHTTP_FLAG_SECURE))
{
if (WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0) && WinHttpReceiveResponse(hRequest, 0))
{
dwError = Download(hRequest);
}
else
{
dwError = GetLastError();
}

WinHttpCloseHandle(hRequest);
}
else
{
dwError = GetLastError();
}

} while (dwError && --nTry);

WinHttpCloseHandle(hConnect);
}

WinHttpCloseHandle(hSession);
}
}

теперь искать Download реализация - содержит ошибки и не удивительно эффективным.

VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_READWRITE);

вы всегда безоговорочно выделять временный буфер памяти (buf), даже если вы основная буфер jar содержащие достаточно свободного пространства. чем перераспределить jar если нужно и скопировать временный буфер к нему memcpy((jar + totalDownloaded), buf, dwDownloaded);

также обратите внимание, что у вас есть потенциальная утечка памяти здесь - WinHttpReadData не - вы просто сломать петли, без безвозмездное временное buf.

на самом деле нам не нужно выделять какую-то временный буфер. нам нужно просто прочитать данные в основной буфер jar - WinHttpReadData(hRequest, (PBYTE)jar + totalDownloaded, dwSize, &dwSize). если jar не хватает свободного места - нам нужно перераспределить ее , прежде чем WinHttpReadData вызова.

теперь посмотрим, как вы перераспределить jar - это просто необъяснимо для меня

realloc: ** goto realloc;

для чего эта петля ?!? почему нужно curalloc = curalloc * 2; и несколько раз выделить, скопировать, бесплатные данные ?!?. когда мы можем просто установить

curalloc = totalDownloaded + dwDownloaded;

еще лучше, конечно, выделить больше памяти, чем totalDownloaded + dwDownloaded. так код для Download может выглядеть так:

ULONG Download(HINTERNET hRequest)
{
ULONG curalloc = 0x10000, dwSize, totalDownloaded = 0, cbNeed;

HANDLE hHeap = GetProcessHeap();

ULONG dwError = NOERROR;

if (PVOID jar = HeapAlloc(hHeap, 0, curalloc))
{
do
{
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
{
dwError = GetLastError();
break;
}

cbNeed = totalDownloaded + dwSize;

if (cbNeed > curalloc)
{
cbNeed = (cbNeed + 0xffff) & ~0xffff;// round to 64kb

if (PVOID buf = HeapReAlloc(hHeap, 0, jar, cbNeed))
{
curalloc = cbNeed, jar = buf;
}
else
{
dwSize = ERROR_OUTOFMEMORY;
break;
}
}

if (!WinHttpReadData(hRequest, (PBYTE)jar + totalDownloaded, dwSize, &dwSize))
{
dwError = GetLastError();
break;
}

totalDownloaded += dwSize;

} while (dwSize);

if (!dwError)
{
DbgPrint("download %u Ok\n", totalDownloaded);
}

HeapFree(hHeap, 0, jar);
}

return GetLastError();
}

это уже хорошо, но не лучший. Windows позволит резервировать пространство памяти. поэтому хорошее решение - в начале зарезервировать большого пространство памяти (в 32-битной можно зарезервировать как минимум П*10 МБ, в 64-битной - гигабайт). а затем выделить память из зарезервированного диапазона. при этом мы не должны перераспределить и копирование данных. класса для данного динамического распределения:

class DynamicBuffer
{
PBYTE _BaseAddress;
SIZE_T _dwReserve, _dwSize, _dwCommit;

static SIZE_T RoundSize(SIZE_T size)
{
static SIZE_T s_dwAllocationGranularity;

if (!s_dwAllocationGranularity)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
s_dwAllocationGranularity = si.dwAllocationGranularity - 1;
}

return (size + s_dwAllocationGranularity) & ~s_dwAllocationGranularity;
}

public:

DynamicBuffer()
{
_BaseAddress = 0, _dwReserve = 0, _dwSize = 0, _dwCommit = 0;
}

~DynamicBuffer()
{
Reset();
}

ULONG Create(SIZE_T dwSize)
{
if (_BaseAddress = (PBYTE)VirtualAlloc(0, dwSize = RoundSize(dwSize), MEM_RESERVE, PAGE_READWRITE))
{
_dwReserve = dwSize;
return NOERROR;
}

return GetLastError();
}

ULONG AllocBuffer(PVOID* ppv, SIZE_T cb)
{
if (_dwReserve - _dwSize < cb)
{
return ERROR_OUTOFMEMORY;
}

SIZE_T dwSize = _dwSize + cb;

if (dwSize > _dwCommit)
{
SIZE_T dwCommit = RoundSize(dwSize);

if (!VirtualAlloc(_BaseAddress + _dwCommit, dwCommit - _dwCommit, MEM_COMMIT, PAGE_READWRITE))
{
return GetLastError();
}

_dwCommit = dwCommit;
}

*ppv = _BaseAddress + _dwSize;

return NOERROR;
}

void AddData(SIZE_T cb)
{
_dwSize += cb;

if (_dwSize > _dwCommit)
{
__debugbreak();
}
}

PVOID getData()
{
return _BaseAddress;
}

SIZE_T getDataSize()
{
return _dwSize;
}

SIZE_T getFreeSpace()
{
return _dwReserve - _dwSize;
}

void Reset()
{
if (_BaseAddress)
{
VirtualFree(_BaseAddress, 0, MEM_RELEASE);
_BaseAddress = 0;
}
_dwReserve = 0, _dwSize = 0, _dwCommit = 0;
}
};

с этим мы можем переписать Download :

ULONG Download(HINTERNET hRequest)
{
DynamicBuffer buf;

ULONG dwError = NOERROR;

if (dwError = buf.Create(0x4000000)) // 64Mb reserve
{
return dwError;
}

ULONG dwSize;

do
{
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
{
dwError = GetLastError();
break;
}

PVOID pv;
if (dwError = buf.AllocBuffer(&pv, dwSize))
{
break;
}

if (!WinHttpReadData(hRequest, pv, dwSize, &dwSize))
{
dwError = GetLastError();
break;
}

buf.AddData(dwSize);

} while (dwSize);

if (!dwError)
{
DbgPrint("download %u Ok\n", buf.getDataSize());
}

return GetLastError();
}

но всегда лучше сделать асинхронную загрузку. сделать это можно сказать следующим образом:

class __declspec(novtable) InternetHandle
{
HINTERNET _hInternet;
LONG _dwRef;

protected:

virtual ~InternetHandle()
{
if (_hInternet)
{
WinHttpCloseHandle(_hInternet);
}
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}

InternetHandle()
{
_dwRef = 1;
_hInternet = 0;
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}

public:

void AddRef()
{
InterlockedIncrement(&_dwRef);
}

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

HINTERNET get_handle() { return _hInternet; }

HINTERNET set_handle(HINTERNET hInternet) {
return InterlockedExchangePointer(&_hInternet, hInternet);
}
};

class CSession : public InternetHandle
{
public:
ULONG Open(LPCWSTR pszAgentW = 0)
{
if (HINTERNET hSession = WinHttpOpen( pszAgentW,
WINHTTP_ACCESS_TYPE_NO_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC))
{
set_handle(hSession);

return NOERROR;
}

return GetLastError();
}
};

class CTarget : public InternetHandle
{
CSession* _pSession;

virtual ~CTarget()
{
_pSession->Release();
}
public:

ULONG Set(PCWSTR pswzServerName, INTERNET_PORT nServerPort)
{
if (HINTERNET hConnect = WinHttpConnect(_pSession->get_handle(), pswzServerName, nServerPort, 0))
{
set_handle(hConnect);
return NOERROR;
}

return GetLastError();
}

CTarget(CSession* pSession) : _pSession(pSession)
{
pSession->AddRef();
}
};

class DownloadCtx : public InternetHandle, public DynamicBuffer, public WINHTTP_ASYNC_RESULT
{
CTarget* _pTarget;
ULONG _nTryCount;
ULONG _dwThreadId;

virtual ~DownloadCtx()
{
DbgPrint("<%u,%u> %u\n", dwResult, dwError, getDataSize());

_pTarget->Release();

PostThreadMessage(_dwThreadId, WM_QUIT, dwResult, dwError);
}

public:
DownloadCtx(CTarget* pTarget, ULONG nTryCount) : _pTarget(pTarget), _nTryCount(nTryCount)
{
pTarget->AddRef();
dwError = ERROR_IO_PENDING, dwResult = 0;
_dwThreadId = GetCurrentThreadId();
}

ULONG SendRequest()
{
if (get_handle()) __debugbreak();

if (!dwError)
{
return NOERROR;
}

if (!_nTryCount--)
{
return dwError;
}

dwError = ERROR_IO_PENDING, dwResult = 0;

Reset();

DbgPrint("=========== %x ============\n", _nTryCount);

if (HINTERNET hRequest = WinHttpOpenRequest(_pTarget->get_handle(), L"GET", NULL,
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_REFRESH|WINHTTP_FLAG_SECURE))
{
PVOID Context = this;

if (WinHttpSetOption(hRequest, WINHTTP_OPTION_CONTEXT_VALUE, &Context, sizeof(Context)))
{
AddRef();

set_handle(hRequest);

WinHttpSetStatusCallback(
hRequest,
_StatusCallback,
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
NULL );

if (!WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
0, (DWORD_PTR)this))
{
Close(GetLastError(), API_SEND_REQUEST);
}

return NOERROR;
}

WinHttpCloseHandle(hRequest);
}

return GetLastError();
}

ULONG Read(HINTERNET hRequest)
{
if (DWORD dwNumberOfBytesToRead = (DWORD)min(0x10000, getFreeSpace()))
{
PVOID Buf;
if (ULONG err = AllocBuffer(&Buf, dwNumberOfBytesToRead))
{
return err;
}

DbgPrint("Begin Read %u\n", dwNumberOfBytesToRead);

return WinHttpReadData(hRequest, Buf, 0x10000, 0) ? NOERROR : GetLastError();
}

return ERROR_BUFFER_OVERFLOW;
}

void Close(ULONG err, DWORD_PTR from)
{
if (HINTERNET hRequest = set_handle(0))
{
dwError = err, dwResult = from;
WinHttpCloseHandle(hRequest);
}
}

static void CALLBACK _StatusCallback(
__in HINTERNET hRequest,
__in DWORD_PTR Context,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
)
{
reinterpret_cast<DownloadCtx*>(Context)->StatusCallback(
hRequest, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);
}

void StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
);
};

void DownloadCtx::StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
)
{
DbgPrint("%x>%p %08x %p %x\n", GetCurrentThreadId(), hRequest,
dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);

ULONG Error;
DWORD_PTR Result;

switch (dwInternetStatus)
{
default: return;

case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
SendRequest();
Release();
return;

case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
Error = reinterpret_cast<WINHTTP_ASYNC_RESULT*>(lpvStatusInformation)->dwError;
Result = reinterpret_cast<WINHTTP_ASYNC_RESULT*>(lpvStatusInformation)->dwResult;
break;

case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
if (WinHttpReceiveResponse(hRequest, NULL))
{
return;
}

Error = GetLastError(), Result = API_RECEIVE_RESPONSE;
break;

case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:

DbgPrint("READ_COMPLETE:%u\n",dwStatusInformationLength);

if (dwStatusInformationLength)
{
AddData(dwStatusInformationLength);

if (Error = Read(hRequest))
{
Result = API_READ_DATA;
break;
}
return ;
}

Error = NOERROR, Result = 0;
break;

case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:

static volatile UCHAR guz;
union {
PWSTR sz;
PVOID buf;
};
PVOID stack = _alloca(guz);
ULONG cb = 0, rcb = 32;
do
{
if (cb < rcb)
{
rcb = cb = (ULONG)((PBYTE)stack - (PBYTE)(buf = _alloca(rcb - cb)));
}

if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH, 0, buf, &rcb, 0))
{
Error = NOERROR;

if (cb = wcstoul(sz, &sz, 10))
{
DbgPrint("Content-Length: %u\n", cb);
cb++;
}
else
{
Error = ERROR_NO_DATA;
}
break;
}
} while ((Error = GetLastError()) == ERROR_INSUFFICIENT_BUFFER);

Result = 0;

switch (Error)
{
case ERROR_WINHTTP_HEADER_NOT_FOUND:
cb = 0x4000000;// 64 mb reserve
case NOERROR:
if (Error = Create(cb))
{
break;
}

if (Error = Read(hRequest))
{
Result = API_READ_DATA;
break;
}

return ;
}
break;
}

Close(Error, Result);
}

void winhttp()
{
if (CSession* pSession = new CSession)
{
if (!pSession->Open())
{
if (CTarget* pTarget = new CTarget(pSession))
{
if (!pTarget->Set(L"stackoverflow.com", INTERNET_DEFAULT_HTTPS_PORT))
{
if (DownloadCtx* Ctx = new DownloadCtx(pTarget, 4))
{
Ctx->SendRequest();

Ctx->Release();
}
}
pTarget->Release();
}
}

pSession->Release();
}

MessageBoxW(0,0,L"for demo",0);
}

2
ответ дан 22 февраля 2018 в 01:02 Источник Поделиться