`
dawuafang
  • 浏览: 1091989 次
文章分类
社区版块
存档分类
最新评论

从Samples中入门IOS开发(五)------ 基于HTTP的网络编程

 
阅读更多

上一篇讲的是如何通过socket进行网络传输,实际上对于互联网上的资源,我们更多的是基于http来开发,SimpleURLConnections展示了如何基于http来进行数据传输,这里主要是讲client如何向http服务器请求和传输数据,http服务器端的实现不在此例子范围之内,实际上就是普通的http服务器。

从本例中主要能学到三点:

  • 基于Get下载文件
  • 基于Put上传文件
  • 基于Post上传文件
基于Get下载文件

首先通过URL打开Connection:

    request = [NSURLRequest requestWithURL:url];
    assert(request != nil);
    
    self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
    assert(self.connection != nil);

然后实现NSURLConnectionDelegate来处理数据传输,其中实现下载图片到本地文件的方法如下:

- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data
    // A delegate method called by the NSURLConnection as data arrives.  We just 
    // write the data to the file.
{
    #pragma unused(theConnection)
    NSInteger       dataLength;
    const uint8_t * dataBytes;
    NSInteger       bytesWritten;
    NSInteger       bytesWrittenSoFar;

    assert(theConnection == self.connection);
    
    dataLength = [data length];
    dataBytes  = [data bytes];

    bytesWrittenSoFar = 0;
    do {
        bytesWritten = [self.fileStream write:&dataBytes[bytesWrittenSoFar] maxLength:dataLength - bytesWrittenSoFar];
        assert(bytesWritten != 0);
        if (bytesWritten == -1) {
            [self stopReceiveWithStatus:@"File write error"];
            break;
        } else {
            bytesWrittenSoFar += bytesWritten;
        }
    } while (bytesWrittenSoFar != dataLength);
}

基于Put上传文件

Put和Get类似,只不过文件上传是通过设置HTTP header来完成的:

self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
assert(self.fileStream != nil);

// Open a connection for the URL, configured to PUT the file.

request = [NSMutableURLRequest requestWithURL:url];
assert(request != nil);

[request setHTTPMethod:@"PUT"];
[request setHTTPBodyStream:self.fileStream];

if ( [filePath.pathExtension isEqual:@"png"] ) {
    [request setValue:@"image/png" forHTTPHeaderField:@"Content-Type"];
} else if ( [filePath.pathExtension isEqual:@"jpg"] ) {
    [request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
} else if ( [filePath.pathExtension isEqual:@"gif"] ) {
    [request setValue:@"image/gif" forHTTPHeaderField:@"Content-Type"];
} else {
    assert(NO);
}

contentLength = (NSNumber *) [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] objectForKey:NSFileSize];
assert( [contentLength isKindOfClass:[NSNumber class]] );
[request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];

self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

其中最主要的是设置三个header属性:method, body和contentLength
[request setHTTPMethod:@"PUT"];
[request setHTTPBodyStream:self.fileStream];
[request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];

基于Post上传文件

基于Post上传文件与Put最大不同点是,http body里除了放入文件本身的数据流之外,还得在文件数据流前后放入结构性的描述信息,比如之前:

bodyPrefixStr = [NSString stringWithFormat:
            @
            // empty preamble
            "\r\n"
            "--%@\r\n"
            "Content-Disposition: form-data; name=\"fileContents\"; filename=\"%@\"\r\n"
            "Content-Type: %@\r\n"
            "\r\n"

之后:

bodySuffixStr = [NSString stringWithFormat:
            @
            "\r\n"
            "--%@\r\n"
            "Content-Disposition: form-data; name=\"uploadButton\"\r\n"
            "\r\n"
            "Upload File\r\n"
            "--%@--\r\n" 
            "\r\n"
            //empty epilogue
            ,
            boundaryStr, 
            boundaryStr
        ];

因此,这里就需要能够把这些数据加入到文件流中,本例采用的方案是生产者和消费者的模式,把这两段信息拼接到文件流中:

[NSStream createBoundInputStream:&consStream outputStream:&prodStream bufferSize:32768];
assert(consStream != nil);
assert(prodStream != nil);
self.consumerStream = consStream;
self.producerStream = prodStream;

self.producerStream.delegate = self;
[self.producerStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.producerStream open];

// Set up our state to send the body prefix first.

self.buffer      = [self.bodyPrefixData bytes];
self.bufferLimit = [self.bodyPrefixData length];

// Open a connection for the URL, configured to POST the file.

request = [NSMutableURLRequest requestWithURL:url];
assert(request != nil);

[request setHTTPMethod:@"POST"];
[request setHTTPBodyStream:self.consumerStream];

[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundaryStr] forHTTPHeaderField:@"Content-Type"];
[request setValue:[NSString stringWithFormat:@"%llu", bodyLength] forHTTPHeaderField:@"Content-Length"];

self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

然后在handleevent中处理数据流的拼接:

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
    // An NSStream delegate callback that's called when events happen on our 
    // network stream.
{
    #pragma unused(aStream)
    assert(aStream == self.producerStream);

    switch (eventCode) {
        case NSStreamEventOpenCompleted: {
            // NSLog(@"producer stream opened");
        } break;
        case NSStreamEventHasBytesAvailable: {
            assert(NO);     // should never happen for the output stream
        } break;
        case NSStreamEventHasSpaceAvailable: {
            // Check to see if we've run off the end of our buffer.  If we have, 
            // work out the next buffer of data to send.
            
            if (self.bufferOffset == self.bufferLimit) {

                // See if we're transitioning from the prefix to the file data.
                // If so, allocate a file buffer.
                
                if (self.bodyPrefixData != nil) {
                    self.bodyPrefixData = nil;

                    assert(self.bufferOnHeap == NULL);
                    self.bufferOnHeap = malloc(kPostBufferSize);
                    assert(self.bufferOnHeap != NULL);
                    self.buffer = self.bufferOnHeap;
                    
                    self.bufferOffset = 0;
                    self.bufferLimit  = 0;
                }
                
                // If we still have file data to send, read the next chunk. 
                
                if (self.fileStream != nil) {
                    NSInteger   bytesRead;
                    
                    bytesRead = [self.fileStream read:self.bufferOnHeap maxLength:kPostBufferSize];
                    
                    if (bytesRead == -1) {
                        [self stopSendWithStatus:@"File read error"];
                    } else if (bytesRead != 0) {
                        self.bufferOffset = 0;
                        self.bufferLimit  = bytesRead;
                    } else {
                        // If we hit the end of the file, transition to sending the 
                        // suffix.

                        [self.fileStream close];
                        self.fileStream = nil;
                        
                        assert(self.bufferOnHeap != NULL);
                        free(self.bufferOnHeap);
                        self.bufferOnHeap = NULL;
                        self.buffer       = [self.bodySuffixData bytes];

                        self.bufferOffset = 0;
                        self.bufferLimit  = [self.bodySuffixData length];
                    }
                }
                
                if ( (self.bufferOffset == self.bufferLimit) && (self.producerStream != nil) ) {
                    self.producerStream.delegate = nil;
                    [self.producerStream close];
                }
            }
            
            // Send the next chunk of data in our buffer.
            
            if (self.bufferOffset != self.bufferLimit) {
                NSInteger   bytesWritten;
                bytesWritten = [self.producerStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
                if (bytesWritten <= 0) {
                    [self stopSendWithStatus:@"Network write error"];
                } else {
                    self.bufferOffset += bytesWritten;
                }
            }
        } break;
        case NSStreamEventErrorOccurred: {
            NSLog(@"producer stream error %@", [aStream streamError]);
            [self stopSendWithStatus:@"Stream open error"];
        } break;
        case NSStreamEventEndEncountered: {
            assert(NO);     // should never happen for the output stream
        } break;
        default: {
            assert(NO);
        } break;
    }
}

基于Post的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics