上一篇讲的是如何通过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
基于Post的数据传输看上去很复杂,实际上道理还是很简单,重点就在于数据流的拼接上。 |