iOS大块上传
我正在尝试将用户通讯录中的联系人流式传输到我们的服务器。 一次将所有联系人拉入内存可能会导致系统崩溃或使设备无响应。 我不想承担将所有联系人写入文件并上传文件的开销。 我可以看到数据通过网络发送,但看起来它的格式无效。 服务器无法识别请求主体。
我正在阅读地址簿中的联系人,并将它们写入NSOutputStream。 这个NSOutputStream通过这段代码与NSInputStream共享一个缓冲区
缓冲用作NSInputStream的NSOutputStream?
//
// NSStream+BoundPairAdditions.m
// WAControls
//
//
#import "NSStream+BoundPairAdditions.h"
#include <sys/socket.h>
static void CFStreamCreateBoundPairCompat(
CFAllocatorRef alloc,
CFReadStreamRef * readStreamPtr,
CFWriteStreamRef * writeStreamPtr,
CFIndex transferBufferSize
)
// This is a drop-in replacement for CFStreamCreateBoundPair that is necessary because that
// code is broken on iOS versions prior to iOS 5.0 <rdar://problem/7027394> <rdar://problem/7027406>.
// This emulates a bound pair by creating a pair of UNIX domain sockets and wrapper each end in a
// CFSocketStream. This won't give great performance, but it doesn't crash!
{
#pragma unused(transferBufferSize)
int err;
Boolean success;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
int fds[2];
assert(readStreamPtr != NULL);
assert(writeStreamPtr != NULL);
readStream = NULL;
writeStream = NULL;
// Create the UNIX domain socket pair.
err = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
if (err == 0) {
CFStreamCreatePairWithSocket(alloc, fds[0], &readStream, NULL);
CFStreamCreatePairWithSocket(alloc, fds[1], NULL, &writeStream);
// If we failed to create one of the streams, ignore them both.
if ( (readStream == NULL) || (writeStream == NULL) ) {
if (readStream != NULL) {
CFRelease(readStream);
readStream = NULL;
}
if (writeStream != NULL) {
CFRelease(writeStream);
writeStream = NULL;
}
}
assert( (readStream == NULL) == (writeStream == NULL) );
// Make sure that the sockets get closed (by us in the case of an error,
// or by the stream if we managed to create them successfull).
if (readStream == NULL) {
err = close(fds[0]);
assert(err == 0);
err = close(fds[1]);
assert(err == 0);
} else {
success = CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
assert(success);
success = CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
assert(success);
}
}
*readStreamPtr = readStream;
*writeStreamPtr = writeStream;
}
// A category on NSStream that provides a nice, Objective-C friendly way to create
// bound pairs of streams.
@implementation NSStream (BoundPairAdditions)
+ (void)createBoundInputStream:(NSInputStream **)inputStreamPtr outputStream:(NSOutputStream **)outputStreamPtr bufferSize:(NSUInteger)bufferSize
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );
readStream = NULL;
writeStream = NULL;
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1070)
#error If you support Mac OS X prior to 10.7, you must re-enable CFStreamCreateBoundPairCompat.
#endif
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (__IPHONE_OS_VERSION_MIN_REQUIRED < 50000)
#error If you support iOS prior to 5.0, you must re-enable CFStreamCreateBoundPairCompat.
#endif
if (NO) {
CFStreamCreateBoundPairCompat(
NULL,
((inputStreamPtr != nil) ? &readStream : NULL),
((outputStreamPtr != nil) ? &writeStream : NULL),
(CFIndex) bufferSize
);
} else {
CFStreamCreateBoundPair(
NULL,
((inputStreamPtr != nil) ? &readStream : NULL),
((outputStreamPtr != nil) ? &writeStream : NULL),
(CFIndex) bufferSize
);
}
if (inputStreamPtr != NULL) {
*inputStreamPtr = CFBridgingRelease(readStream);
}
if (outputStreamPtr != NULL) {
*outputStreamPtr = CFBridgingRelease(writeStream);
}
}
@end
在这里,我通过处理NSOutputStream委托来构建请求体。
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasSpaceAvailable: {
if(self.contactIndex == 0 && [self.producerStream hasSpaceAvailable]) {
NSMutableData *data = [[NSMutableData alloc] init];
[data appendData:[@"rnrnrn" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[@"{"contacts": [" dataUsingEncoding:NSUTF8StringEncoding]];
[self.producerStream write:[data bytes] maxLength:[data length]];
}
while([self.producerStream hasSpaceAvailable] && self.contactIndex < [self.dataContactIDs count]) {
NSMutableData *contactData = [[[self getNextContact] dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
if(self.contactIndex < [self.dataContactIDs count]) {
[contactData appendData:[@"," dataUsingEncoding:NSUTF8StringEncoding]];
}
[self.producerStream write:[contactData bytes] maxLength:[contactData length]];
}
if(self.contactIndex == self.dataContactIDs.count) {
NSMutableData *data = [[NSMutableData alloc] init];
[data appendData:[@"]}" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[@"rnrnrn" dataUsingEncoding:NSUTF8StringEncoding]];
[self.producerStream write:[data bytes] maxLength:[data length]];
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
stream = nil;
}
} break;
case NSStreamEventHasBytesAvailable: {
} break;
case NSStreamEventErrorOccurred: {
} break;
case NSStreamEventEndEncountered: {
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
stream = nil;
} break;
default: {
} break;
}
}
我正在使用AFNetworking来进行联网。 我将请求正文流设置为NSInputStream。
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:@"POST"];
[request setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBodyStream:inputStream];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFHTTPResponseSerializer serializer];
[op setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(@"PROGRESS %d %lld %lld", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[self processResponse:responseObject success:success error:error log:log];
} failure:^(AFHTTPRequestOperation *operation, NSError *e) {
[self processError:e op:operation error:error log:log];
}];
[[NSOperationQueue mainQueue] addOperation:op];
然后网络请求会这样:(使用Wireshark捕获)
POST /upload?token=dd224bceb02929b36d35&agent=iPhone%20Simulator&v=1.0 HTTP/1.1
Host: localhost:6547
Transfer-Encoding: Chunked
Accept-Encoding: gzip, deflate
Content-Type: application/json; charset=UTF-8
Accept-Language: en-us
Connection: keep-alive
Accept: */*
User-Agent: MyApp/2.0 CFNetwork/672.0.8 Darwin/13.0.0
9BD
{"contacts": [(valid json array)]}
0
我不确定为什么9BD和0包含在请求主体中。 我认为这是一个错误,如何设置缓冲区,我相信这会导致服务器忽略http正文,因为它是无效的。 它看起来像我正确构建请求? 有一个更好的方法吗? 我使用金字塔/ python来处理请求。 服务器收到请求,但请求正文为空。
编辑
如果我不发送任何联系人,“9BD”将消失。 如果更改联系人数据,“9BD”将更改为不同的字符。 “0”总是在最下面。
编辑2
Jim指出请求的格式是有效的。 这意味着服务器没有正确处理流。 该请求是击中服务器好,并且服务器回复正常。 但是,我没有看到任何请求正文。 服务器正在运行金字塔/ python。 在服务器上,request.body是空的。
您的流处理程序委托不正确:
在这里,当你将数据写入producerStream时:
[self.producerStream write:[data bytes] maxLength:[data length]];
可能会发生这样的情况: NSData
对象中的所有字节都不能写入流中。 发生这种情况时,会丢失字节。
为了解决这个问题,你需要检查write:maxLength:
的返回值,它等于写入的字节数(或者表示一个错误)。 然后,您需要保存NSData
对象的状态以及您写入流中的该数据对象的字节范围。 在下一个事件循环中,您需要检查数据是否有剩余字节,并继续写入字节,直到写入所有字节。
实际上,这种任务的强大实施是非常棘手和容易出错的。
我想分享一些代码,将一个流复制到另一个流中,并且已经过测试:
RXStreamToStreamCopier
这段代码可能会给你一个跳跃的开始。 类RXStreamToStreamCopier
将RXStreamToStreamCopier
复制到目标流中。 这些流将在您可以指定的运行循环中进行安排。 这个类就像一个可以启动和取消的NSOperation
。
在内部,该类使用固定大小的传输缓冲区和pull
方法从源流读取数据,并使用push
方法写入目标数据流。 您可以覆盖push
方法来转换源字节。
您使用创建一个RXStreamToStreamCopier
对象
- (id) initWithSourceStream:(NSInputStream*)sourceStream
destinationStream:(NSOutputStream*)destinationStream;
源流将与您的数据源相关联。 目标流通常是有界流对的一半。 有界流对的另一端 - 输入流 - 然后可以最终设置请求的属性HTTPBodyStream
。
您可以按原样使用它,但它取决于另一个库RXPromise。
提示使用分块传输编码的解决方法:
如果您事先知道流式字节的大小,则可以明确设置Content-Length标头。 这将导致NSURLConnection
不使用分块传输编码。
正如@Rob在注释中所述, NSURLSession
行为将有所不同:如果将一个流设置为输入,则将移除Content-Length标头,这将导致分块传输编码。
这个要求很好。 你的请求被分块:
Transfer-Encoding: Chunked
9BD
表示下一个块的长度。 最后的零表示没有更多的块。
有关详细信息,请参阅RFC 2616的第3.6.1节。
您的问题可能是您的服务器不理解分块请求。
链接地址: http://www.djcxy.com/p/17385.html上一篇: iOS Chunked Upload
下一篇: Type "char" in C++