1

I need to get image information from server, such image name, image id. Then use image id as one of parameters to make post, get image actual data. More specific, there are three images I should get.

First, I use getImageInfo to get image information.

- (void)getImageInfo {
// compose request
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:@"uid"];
NSString *checkCode = [getUserInfo objectForKey:@"checkCode"];
NSString *data = [NSString stringWithFormat:@"uid=%@&yangzhengma=%@", uid, checkCode];

NSURL *url = [NSURL URLWithString:@"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetallshibietu"];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPMethod = @"POST";

[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

    if (!error) {

        NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
        if (httpResp.statusCode == 200) {

            // parse data in ram and put into images' imageInfos array
            [self.images parseImageInfo:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];

            [self getImageRawData];

            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableView reloadData];
            });
        }
    }
}] resume];}

Then I use getImageRawData to get three image data.

- (void)getImageRawData {
// compose request dynamically
NSUserDefaults *getUserInfo = [NSUserDefaults standardUserDefaults];
NSString *uid = [getUserInfo objectForKey:@"uid"];
NSString *checkCode = [getUserInfo objectForKey:@"checkCode"];
NSURL *url = [NSURL URLWithString:@"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
NSInteger count = 0;

for (ImageInformation *temp in self.images.imageInfos) {
    NSString *data = [NSString stringWithFormat:@"uid=%@&yangzhengma=%@&tupianid=%@", uid, checkCode, temp.imageId];

    request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

        // if client side is no errors, continue
        if (!error) {

            // if server side is no errors, continue
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 200) {

                NSLog(@"图片内容:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

                // in ram and put into images' imageRawData array
                [self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId withIndex:count];

                // store data to disk
                //                    NSString *path = [[NSString alloc] initWithFormat:@"image%@", temp.imageId];
                //                    [FCFileManager writeFileAtPath:path content:data];

                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.tableView reloadData];
                });
            }
        }
    }] resume];

    count++;
}}

Here, it will loop three times, three responses come back, only the last one is complete, the others carry a error message, or incomplete raw data sometimes. Now I'm diving into concurrency programming guide, I guess serial queue likely can solve this problem.

Output like this:

2014-12-16 22:38:48.739 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:48.749 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>error</ns:return></ns:serviceGetthetupianResponse>
2014-12-16 22:38:51.943 WeddingNewVersion[997:83366] 图片内容:<ns:serviceGetthetupianResponse xmlns:ns="http://serviceimpl.my.com"><ns:return>/9j/...(complete data)...9k=%%226654474.0</ns:return></ns:serviceGetthetupianResponse>

parameters of requests:

2014-12-17 14:59:25.364 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=41
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=42
2014-12-17 14:59:25.368 WeddingNewVersion[1875:226651] uid=6&yangzhengma=odWoDXWcBv1jOrEhywkq7L&tupianid=43

the problem is likely not in composing request.

------------------------------------------------update1-----------------------------------------------

I have tried to put data task of session into a serial queue. Disappointed, this is not working.

        dispatch_async(self.serialQueue, ^{
        [[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
    });

Meanwhile, I make delegateQueue of session as nil, reference says if nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.

Now I am still confused how to make it right.

-----------------------------------------------update2------------------------------------------------

I add [NSThread sleepForTimeInterval:0.5] into the block dispatched to serial queue.

        dispatch_async(self.serialQueue, ^{
        [[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){...}] resume];
        [NSThread sleepForTimeInterval:0.5];
    });

It does not work. The three responses are complete, but they are all the same.

Thank you in advance!

attolee
  • 602
  • 1
  • 9
  • 22
  • @Rob Using the same mutable request, I didn't thought too much, the different part of three requests is the body, so put the changeable part into loop. The requests are accepted by server, I have made a judgement: if (httpResp.statusCode == 200), you see, only if response with a HTTP status code of 200, then it will output the data. – attolee Dec 17 '14 at 02:55
  • Do you have access to the HTTP server logs? That is, is it your own server, and not something like Facebook? If you can please post the log entries for the three requests. – Mike Crawford Dec 17 '14 at 03:15
  • @MichaelCrawford I have discussed with the server guy, error message when parameters of request are wrong, or in the condition just as you guessed. Is it necessary to have server log? – attolee Dec 17 '14 at 03:40
  • No, it's not strictly necessary to have the server log, but it might be helpful. If it would be hard to get the log, don't sweat it, I'll figure this out for you. However I am very very tired - I got up very early this morning, so I need to sleep. But I'd be happy to come up working code for you first thing tomorrow morning. – Mike Crawford Dec 17 '14 at 06:02
  • @Rob I have logged data strings out, the problem is likely not here. Could you tell me why you are prefer to use three different request objects? – attolee Dec 17 '14 at 07:26

3 Answers3

0

I'm just guessing as I've never tried it, but possibly your data tasks are all using the same TCP port on your end.

That would be OK if they were serialized - one after the other, in sequence - but if they overlap, then the server would receive garbled HTTP requests:

GET /foo
GET /bar
GET /baz

What the server would see might be something like:

GET /fGET /baroo
GET /baz

That your third requests actually works OK might be an accident of the timing.

If you absolutely require the three requests to be issued simultaneously, there are ways to open three different ports on your end. I don't know how to do it with Cocoa and Objective-C, but you can certainly do it with C and Berkeley Socket system calls. The Cocoa / Cocoa Touch networking methods are just wrappers around sockets.

Mike Crawford
  • 2,232
  • 2
  • 18
  • 28
  • Thank you for your answer. I'm stuck with this problem for several days. It seems a lot of materials to be read in order to use c and berkeley socket system call. May I ask you for more help? take you some time to make a example about how to make them serialized with Cocoa and OC, i don't need to make requests simultaneous. – attolee Dec 17 '14 at 02:27
  • I'm happy to help, but not just now as I am at a presentation. How urgently do you need your solution? I don't think it would be hard for me to come up with code that will work, however I can't stay up late tonight as I need to get some sleep. – Mike Crawford Dec 17 '14 at 03:13
  • Sleep is more important, get your sleep, then help me when you are convenient. Thanks again, good night. – attolee Dec 17 '14 at 03:28
  • Nighty-Night! Don't Let The Code Bugs Byte. – Mike Crawford Dec 17 '14 at 06:03
  • Michael, while this is some creative brain-storming, HTTP requests simply do not suffer from this sort of behavior. This is confirmed by the fact that, he's getting three responses with status code 200, which means that his three distinct requests were successfully received. So, it's not a transport-layer sort of problem. It's likely some application-level problem in the server which is preventing concurrent requests from being satisfied. – Rob Dec 17 '14 at 15:39
0

A couple of thoughts:

  1. Your technique of using a single NSMutableURLRequest instance, and repeatedly mutating it for each request (while the prior requests are still in progress) is curious.

    In the spirit of thread safety, I would use a separate NSMutableURLRequest for each concurrent request. You don't want to risk having your thread issuing these requests mutate the request object while some background thread performing one of the prior requests. (See Apple's Thread Safety Summary in the Threading Programming Guide in which they point out that mutable classes are not generally thread safe.)

    Having said that, the NSURLConnection documentation leaves us with the impression that this request object would be copied, mitigating this problem. I don't see this sort of assurance in the NSURLSession documentation (though I suspect it does the same thing).

    I don't think this is the problem here (if this was the problem, the problem would likely be more erratic than what you report, and besides, I suspect that NSURLSession is handling this gracefully, anyway), but as a matter of good thread-safe coding habits, it would be prudent to let each concurrent request have its own NSMutableURLRequest object.

  2. You have confirmed that the information being used in the requests looks valid.

    If you wanted to take this to the next level, you might use Charles (or Wire Shark or whatever tool you prefer) to observe the actual requests as they go out. These sorts of tools are invaluable for debugging these sorts of problems.

    If you observe the requests in Charles and confirm that they are valid, then this categorically eliminates client-side issues from the situation.

  3. What is curious is that you are not receiving NSError object from dataTaskWithRequest. Nor are you receiving statusCode other than 200 from your server. That means that your requests were successfully sent to the server and received by the server.

    Instead, the server is processing the request, but is having a problem fulfilling the request. This leads me to wonder about the server code, itself. I suspect that there is something in the server code that is preventing concurrent operations from taking place (e.g., locking some shared resource, such as temp file or SQL table, for the duration of the request). I would take a hard look at the server code and make sure there are no potential contention issues.

    Furthermore, I would modify the server code to not simply report "error", but rather to produce a meaningful error message (e.g. system provided error messages, error codes, etc.). Your server is detecting an error, so you should have it tell you precisely what that error was.

Note, I am explicitly not advising you to make your requests run sequentially. That is inadvisable. While it might solve the immediate problem, you pay a huge performance penalty doing that, and it's not scalable. And remember, you really must handle concurrent requests gracefully, as you're likely to have multiple users of the app at some point.

I would take a hard look at the server code, adding further debugging information to the error messages in order to track down the problem.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

I put request into for loop, it works. The first thought of rob about NSMutableRequest and NSURLSession seems right, I'm trying to catch the whole idea. Thanks for rob's answer. Anyway, this is code.

for (ImageInformation *temp in self.images.imageInfos) {
    // compose request dynamically
    NSURL *url = [NSURL URLWithString:@"http://121.199.35.173:8080/xihuan22dcloud/services/Shibietupianservice/serviceGetthetupian"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    NSString *data = [NSString stringWithFormat:@"uid=%@&yangzhengma=%@&tupianid=%@", uid, checkCode, temp.imageId];

    request.HTTPBody = [data dataUsingEncoding:NSUTF8StringEncoding];

    // data task
    dispatch_async(self.serialQueue, ^{
        [[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

            // if client side is no errors, continue
            if (!error) {

                // if server side is no errors, continue
                NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
                if (httpResp.statusCode == 200) {

                    // in ram and put into images' imageRawData array
                    [self.images parseImageRawData:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] withImageId:temp.imageId];

                    // store data to disk
                    //                    [FCFileManager writeFileAtPath:path content:data];


                    // dispatch display image task to main
                    dispatch_async(dispatch_get_main_queue(), ^{
                        if ([self.images.imageDrawDatasDic count] == [self.images.imageInfos count]) {
                            [self.tableView reloadData];
                        }
                    });
                }
            }
        }] resume];
        [NSThread sleepForTimeInterval:0.5];
    });
}

}

attolee
  • 602
  • 1
  • 9
  • 22