How to calculate payload size and timeout length f

2019-08-23 06:10发布

问题:

My app offers the option to download 3430 high resolution images from our server, each image of size 50k - 600k bytes.

The original approach was to just download all of them - but we realized that gave a lot of NSURLErrorTimedOut errors and crashed our program. We've now implemented it such that we download all of the images, but in batches of 100 images at a time.

- (void)batchDownloadImagesFromServer:(BOOL)downloadHiResImages
{

    [UIApplication sharedApplication].idleTimerDisabled = YES;
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];

    [self generateImageURLList:YES];

    [leafletImageLoaderQueue removeAllObjects];
    numberOfThumbnailLeft = [uncachedThumbnailArray count];
    numberOfHiResImageLeft = [uncachedHiResImageArray count];

    NSLog(@"DEBUG: In batchDownloadImagesFromServer numberOfThumbnailLeft %ul , numberOfHiResImageLeft %ul ", numberOfThumbnailLeft, numberOfHiResImageLeft);

    numberOfImagesToDownload = numberOfThumbnailLeft;
    if (downloadHiResImages)
    {
        numberOfImagesToDownload += numberOfHiResImageLeft;
    }

    if (numberTotalToDownload < 0) {
        numberTotalToDownload = numberOfHiResImageLeft;
    }

    int midBatchCt = 0;
    // start where we stopped
    NSArray *subArray;
    NSRange batchRange;
    batchRange.location = 0;//uncachedHiResIndex;
    NSInteger uncachedNumber = [uncachedHiResImageArray count];
    NSLog(@"uncachedHiResIndex and numberTotalToDownload: %d %d", uncachedHiResIndex, numberTotalToDownload);
    if (uncachedHiResIndex >= numberTotalToDownload || batchRange.location >= uncachedNumber) {
        // we have reached the end of the uncached hires images

        NSLog(@" END of download total to download=%ld , uncachedNumber=%ld, # not downloaded is %ld", (long)numberTotalToDownload, uncachedNumber, (long)numberFailedToDownload);
        return;
    }
    if (batchRange.location+100 > uncachedNumber) {
        NSInteger imagesUntilEnd = uncachedNumber -1;
        batchRange.length = imagesUntilEnd;
        NSLog(@"this is uncached number: %d this is uncachedhiresindex:%d and this images until end:%d ", uncachedNumber, uncachedHiResIndex, imagesUntilEnd);

    } else {
        batchRange.length = 100;
    }
    NSLog(@" NEW RANGE is from %lul to %lul ", (unsigned long)batchRange.location, batchRange.length);
    subArray = [uncachedHiResImageArray subarrayWithRange:batchRange];
    if (downloadHiResImages)
    {
        for ( LeafletURL* aLeafletURL in subArray )
        {
            LeafletImageLoader* hiResImageLoader = [[LeafletImageLoader alloc] initWithDelegate:self];
            [leafletImageLoaderQueue addObject:hiResImageLoader]; // do this before making connection!! //

            [hiResImageLoader loadImage:aLeafletURL isThumbnail:NO   isBatchDownload:YES];

            //// Adding object to array already retains it, so it's safe to release it here. ////
            [hiResImageLoader release];

            midBatchCt++;
            uncachedHiResIndex++;
            if (midBatchCt == 10) {
                NSLog(@" Waiting for queued images to download...");
                NSLog(@" REMOVED from QUEUE %lul , UncachedIndex %lul", numberRemovedFromQueue, uncachedHiResIndex);
                break;
            }
        }
    }



    if ( [leafletImageLoaderQueue count] == 0 && numberRemovedFromQueue == numberTotalToDownload) {
        if([delegate respondsToSelector:@selector(requestDidBatchDownloadImages:)])
        {
            [delegate requestDidBatchDownloadImages:self];
        }
    }


}

This has resolved most of our issues. However, we would like to test for network connectivity before even beginning batch downloading. I found a low level ping library that gives accurate round-trip timing results. Using the demo code from GBPing as a reference, I wrote my own code to ping our server before we call batchDownloadImagesFromServer.

- (IBAction)preloadAll:(id)sender
{
self.ping = [GBPing new];
    self.ping.host = kDefaultDataServer;
    self.ping.delegate = self;
    self.ping.timeout = 1;
    self.ping.pingPeriod = 0.9;

    // setup the ping object (this resolves addresses etc)
    [self.ping setupWithBlock:^(BOOL success, NSError *error) {
        if (success) {
            // start pinging
            [self.ping startPinging];

            // stop it after 5 seconds
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"stop it");
                [self.ping stop];
                self.ping = nil;
            });
        } else {
            UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Internet Connection"
                                                                            message:@"Not strong enough internet connection"
                                                                     preferredStyle:UIAlertControllerStyleAlert];

            UIAlertAction* OKButton = [UIAlertAction
                                        actionWithTitle:@"Ok"
                                        style:UIAlertActionStyleCancel
                                        handler:^(UIAlertAction * action) {
                                            [downloadManager batchDownloadImagesFromServer:YES];
                                        }];

            [alert addAction:OKButton];
            [self presentViewController:alert animated:NO completion:nil];
        }

    }];
}

I am completely new to networking. How do I determine the payload size and timeout length for my test considering the batch size and the size of the images?

回答1:

Timeout length is per request. It is just the time the networking code will wait for a reply before it gives up. This shouldn't be too short, but for most system API, timeout length is something around a minute or more, which is probably too long.

Also, note that you will still get time out errors if connectivity is bad, so whatever caused your crashes needs to be fixed. You have to be able to recover from time-outs.

You're not giving much information about your crash (what kind of crash is it? What backtrace do you get?), but I can see three obvious things that may be happening:

  1. You did your downloading in a tight loop without an @autoreleasepool {} block inside it. This means that all your downloaded file data accumulated in RAM and blew your app's memory limit. Be sure to put autorelease pools in long-running loops.

  2. You were doing these downloads on the main thread. The main thread is for the UI and short actions. If your main thread code does anything that takes longer than a few seconds, UIApplication will not get to process touch events from the user (and other occurrences) and the operating system will shoot it down as being unresponsive. Offload longer operations onto a dispatch queue (or use some other way to move the actions off the main thread, e.g. by using asynchronous API).

  3. You are flooding your server with requests, and some DDoS-protection inside it decides to just ignore requests from you for a few minutes as a form of self-protection. Many servers have limits on how many requests they will accept from a client within a given period of time, or how many open connections a client may have at the same time.

I think you would be much better served by showing the code that performs the actual download. You should not need to get accurate ping timing information to download a bunch of image files.

Assuming one or more of the above possibilities are true, I'd suggest you implement your download code like this:

  1. Create a list of all file URLs that need to be downloaded.

  2. Write your code so that it downloads these URLs sequentially. I.e. do not let it start downloading a file until the previous one has finished (or failed and you decided to skip it for now).

  3. Use NSURLSession's support for downloading an individual file to a folder, don't use the code to get an NSData and save the file yourself. That way, your application doesn't need to be running while the download finishes.

  4. Ensure that you can tell whether a file has already been downloaded or not, in case your download gets interrupted, or the phone is restarted in mid-download. You can e.g. do this by comparing their names (if they are unique enough), or saving a note to a plist that lets you match a downloaded file to the URL where it came from, or whatever constitutes an identifying characteristic in your case.

  5. At startup, check whether all files are there. If not, put the missing ones in above download list and download them sequentially, as in #2.

  6. Before you start downloading anything (and that includes downloading the next file after the previous download has finished or failed), do a reachability check using the Reachability API from Apple's SystemConfiguration.framework. That will tell you whether the user has a connection at all, and whether you're on WiFi or cellular (in general, you do not want to download a large number of files via cellular, most cellular connections are metered).

If your images are stored on separate servers, or they are comparatively small and there is more overhead setting up the connection than actually downloading the data, you could modify the code to download several images at once, but usually if you're downloading more than 4 images from a server at the same time, you'll likely not see a performance benefit, as every additional image will just reduce the amount of bandwidth available for the others.