1

I'm using WinHTTP APIs in a C++ code similar to the one at the bottom of this article. It runs from my Windows service, and is used to download updates in the background. The code works fine, except that I've received complaints that when it is downloading an update that code uses up too much bandwidth available on a client computer.

Is there a way to make those WinHTTP APIs, WinHttpQueryDataAvailable and WinHttpReadData in particular, limit how much bandwidth they use? Say, up to 30% of the available bandwidth.

PS. For the ease of reference I'm going to copy the code I'm referring from the MSDN article:

DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer;
BOOL  bResults = FALSE;
HINTERNET  hSession = NULL, 
           hConnect = NULL,
           hRequest = NULL;

// Use WinHttpOpen to obtain a session handle.
hSession = WinHttpOpen( L"WinHTTP Example/1.0",  
                        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                        WINHTTP_NO_PROXY_NAME, 
                        WINHTTP_NO_PROXY_BYPASS, 0);

// Specify an HTTP server.
if (hSession)
    hConnect = WinHttpConnect( hSession, L"www.microsoft.com",
                               INTERNET_DEFAULT_HTTPS_PORT, 0);

// Create an HTTP request handle.
if (hConnect)
    hRequest = WinHttpOpenRequest( hConnect, L"GET", NULL,
                                   NULL, WINHTTP_NO_REFERER, 
                                   WINHTTP_DEFAULT_ACCEPT_TYPES, 
                                   WINHTTP_FLAG_SECURE);

// Send a request.
if (hRequest)
    bResults = WinHttpSendRequest( hRequest,
                                   WINHTTP_NO_ADDITIONAL_HEADERS,
                                   0, WINHTTP_NO_REQUEST_DATA, 0, 
                                   0, 0);


// End the request.
if (bResults)
    bResults = WinHttpReceiveResponse( hRequest, NULL);

// Keep checking for data until there is nothing left.
if (bResults)
{
    do 
    {
        // Check for available data.
        dwSize = 0;
        if (!WinHttpQueryDataAvailable( hRequest, &dwSize)) 
        {
            printf( "Error %u in WinHttpQueryDataAvailable.\n",
                    GetLastError());
            break;
        }

        // No more available data.
        if (!dwSize)
            break;

        // Allocate space for the buffer.
        pszOutBuffer = new char[dwSize+1];
        if (!pszOutBuffer)
        {
            printf("Out of memory\n");
            break;
        }

        // Read the Data.
        ZeroMemory(pszOutBuffer, dwSize+1);

        if (!WinHttpReadData( hRequest, (LPVOID)pszOutBuffer, 
                              dwSize, &dwDownloaded))
        {                                  
            printf( "Error %u in WinHttpReadData.\n", GetLastError());
        }
        else
        {
            printf("%s", pszOutBuffer);
        }

        // Free the memory allocated to the buffer.
        delete [] pszOutBuffer;

        // This condition should never be reached since WinHttpQueryDataAvailable
        // reported that there are bits to read.
        if (!dwDownloaded)
            break;

    } while (dwSize > 0);
}
else
{
    // Report any errors.
    printf( "Error %d has occurred.\n", GetLastError() );
}

// Close any open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);

EDIT: While following up on @RemyLebeau suggestions, I created a test C++ project (you can download it from here) that attempts to calculate the current download rate used by the method above and use "Sleep" API to throttle itself. Unfortunately the results I'm getting from it are quite unexpected. I made a screenshot:

enter image description here

See the difference between my reading and what Task Manager is giving me. (Note that nothing was using bandwidth at the time I was running these tests.)

I must be missing something. The question is what?

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • If your buffer size is small(ish) you could sleep the thread for an appropriate amount of time so that usage over the duration of the download would equate to ~30% bandwidth. – user1793036 Nov 11 '14 at 03:48
  • @user1793036: Well, I understand the theory. Can you show what you mean in the code? – c00000fd Nov 11 '14 at 03:51

1 Answers1

1

It is not always easy to throttle by "30% of available bandwidth", as you would have to know what the "available bandwidth" actually is, and that may not always be easy to determine programmably. I suppose you could clock each loop iteration to calculate the probable bandwidth based on how long each read takes. But that very well may fluctuate as bandwidth is used for other things, and as you throttle your bandwidth usage, your calculation of the available bandwidth would be affected.

What is more common (and typically easier) to implement is to throttle by desired "bytes per (milli)second" instead. You can't throttle WinHttpReadData() itself, but you can throttle how often you call it. Just keep track of how many bytes you are reading and sleep your loop iterations so you don't read too many bytes past your desired throttle speed - sleeping longer to slow down, and sleeping shorter to speed up, eg:

// Keep checking for data until there is nothing left.
if (bResults)
{
    char *pszOutBuffer = NULL;
    DWORD dwOutBufferSize = 0;

    do 
    {
        // Check for available data.

        // RL: personally, I would not bother with WinHttpQueryDataAvailable()
        // at all.  Just allocate a fixed-size buffer and let WinHttpReadData()
        // tell you when there is no more data to read...

        dwSize = 0;
        if (!WinHttpQueryDataAvailable( hRequest, &dwSize)) 
        {
            printf( "Error %u in WinHttpQueryDataAvailable.\n", GetLastError());
            break;
        }

        // No more available data.
        if (!dwSize)
            break;

        // (re)Allocate space for the buffer.
        if (dwSize > dwOutBufferSize)
        {
            delete [] pszOutBuffer;
            pszOutBuffer = NULL;
            dwOutBufferSize = 0;

            pszOutBuffer = new char[dwSize];
            if (!pszOutBuffer)
            {
                printf("Out of memory\n");
                break;
            }

            dwOutBufferSize = dwSize;
        }

        // Read the Data.
        DWORD dwStart = GetTickCount();
        if (!WinHttpReadData(hRequest, pszOutBuffer, dwSize, &dwDownloaded))
        {                                  
            printf("Error %u in WinHttpReadData.\n", GetLastError());
            break;
        }
        DWORD dwEnd = GetTickCount();
        DWORD dwDownloadTime = (dwEnd >= dwStart) ? (dwEnd - dwStart) : ((MAXDWORD-dwStart)+dwEnd);
        if (dwDownloadTime == 0) dwDownloadTime = 1;

        printf("%.*s", dwDownloaded, pszOutBuffer);

        // throttle by bits/sec
        //
        // todo: use a waitable object to sleep on, or use smaller
        // sleeps more often, if you need to abort a transfer in
        // progress during long sleeps...

        __int64 BitsPerSec = (__int64(dwDownloaded) * 8) * 1000) / dwDownloadTime;
        if (BitsPerSec > DesiredBitsPerSec)
            Sleep( ((BitsPerSec - DesiredBitsPerSec) * 1000) / DesiredBitsPerSec );
    }
    while (true);

    // Free the memory allocated to the buffer.
    delete [] pszOutBuffer;
}
else
{
    // Report any errors.
    printf( "Error %d has occurred.\n", GetLastError() );
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks. So you're using 1,000 bits to convert to `kbs` and not 1,024, right? – c00000fd Nov 11 '14 at 07:30
  • And also, in your code you use whatever `WinHttpReadData` returns in `dwDownloaded` parameter. But what if say, the server returns 1GB in one "big gulp"? Could there be a need to break it down to `1000 / 8` byte buffer chunks? – c00000fd Nov 11 '14 at 07:32
  • And one more thing I just noticed. When you calculate your `SleepInterval` you don't time how long it takes to download `dwDownloaded` bytes to get `kbs` throughput. So I'm not really following that part... – c00000fd Nov 11 '14 at 07:35
  • @c00000fd: The `1000` refers to milliseconds (1000ms = 1sec), not how many bytes are in a kb. The code is calculating the number of bits downloaded (`dwDownloaded * 8`) and multiplying that by 1000 to get the current number of milliseconds needed to transfer that many bits at the current bandwidth, then dividing those milliseconds by the desired speed to get the sleep interval needed to maintain that speed. Also, a socket would never return "1GB in one big gulp", you will get a few 1000 KB or maybe 1 MB or two, at most, depending on the default kernel buffer size. – Remy Lebeau Nov 11 '14 at 16:52
  • Hmm. Sorry, I still don't see how that would work w/o knowing how long it took to download `dwDownloaded` bytes. Am I missing something? Does `WinHttpReadData` meter it out, and, say, download for only 1 second or for some predefined amount of time? Also your `Sleep(MAXDWORD)` construct is somewhat puzzling. `MAXDWORD` ms would equal to about 49 days. Will your code expect to wait for that long? I understand that you're probably trying to unwind the 64bit variable, but it makes no sense from a practical standpoint... – c00000fd Nov 11 '14 at 22:40
  • I adjusted the algorithm to take the duration of `WinHttpReadData()` into account. – Remy Lebeau Nov 11 '14 at 23:25
  • My math is a little rusty, I hope I got it right this time. – Remy Lebeau Nov 11 '14 at 23:30
  • Yeah, now it seems right. Although you can just do `DWORD dwDownloadTime = GetTickCount() - dwStart;`. No need for complexity that you put there. But there's still an issue. I'm testing it and the reading for `BitsPerSec` doesn't match. I use the Task Manager on Windows 8 , `Performance` tab for the actual network throughput. So it gives me a value in `Mbps`. To convert to it I do `BitsPerSec / (1024 * 1024)` and it is way off. I mean, not even close... – c00000fd Nov 12 '14 at 00:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/64750/discussion-between-remy-lebeau-and-c00000fd). – Remy Lebeau Nov 12 '14 at 01:07