iOS分块上传

2024-10-01 17:23:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试将联系人从用户的地址簿流式传输到我们的服务器。一次将所有联系人拉入内存可能会导致设备崩溃或无响应。我不想承担将所有联系人写入文件并上载文件的开销。我可以看到数据是通过网络发送的,但看起来格式无效。服务器无法识别请求正文。在

我正在从地址簿中读取联系人并将其写入NSOutputStream。此NSOutputStream通过以下代码与NSInputStream共享一个缓冲区

Buffering NSOutputStream used as NSInputStream?

//
//  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委派来构建请求主体。在

^{pr2}$

我在用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主体,因为它是无效的。看起来我构建的请求正确吗?有更好的方法吗?我使用pyramid/python来处理请求。服务器接收请求正常,但请求正文为空。在

编辑

如果我不发送任何联系人,“9BD”就会消失。如果我更改联系人数据,“9BD”将更改为不同的字符。“0”总是在底部。在

编辑2

Jim指出请求的格式是有效的。这意味着服务器没有正确处理流。请求正确命中服务器,服务器响应正常。但是,我没有看到任何请求体。服务器正在运行pyramid/python。在服务器上,请求.正文是空的。在


Tags: to服务器if联系人errorassertnullsuccess
2条回答

这个要求很好。您的请求被分块:

Transfer-Encoding: Chunked

9BD表示下一个块的长度。末尾的零表示不再有块。在

有关详细信息,请参见section 3.6.1 of RFC 2616。在

您的问题可能是您的服务器不理解分块请求。在

流处理程序委托不正确:

在这里,当您将数据写入producerStream时:

[self.producerStream write:[data bytes] maxLength:[data length]];

可能的情况是,NSData对象中的所有字节都可能写入流中。当这种情况发生时,就会丢失字节。在

为了解决这个问题,您需要检查write:maxLength:的返回值,该值等于写入的字节数(或指示错误)。然后您需要保存NSData对象的状态,以及写入流中的数据对象的字节范围。在下一个事件循环中,您需要检查数据中是否还有剩余的字节,并继续写入字节,直到所有字节都被写入为止。在

实际上,这种任务的健壮实现是相当棘手和容易出错的。在

我想分享一些代码,它将一个流复制到另一个流中,并且已经过测试:

RXStreamToStreamCopier

这段代码可能会给你一个跳跃式的开始。类RXStreamToStreamCopier将源流复制到目标流中。流是在运行循环上调度的,您可以指定该循环。类就像一个NSOperation,可以启动和取消。在

在内部,该类使用固定大小的传输缓冲区(transfer buffer)和pull方法读取源流,使用push方法写入目标流。您可以重写push方法来转换源字节。在

使用创建一个RXStreamToStreamCopier对象

^{pr2}$

源流将与数据源相关联。目标流通常是有界流对的一半。有界流对的另一端-输入流-可以最终设置请求的属性HTTPBodyStream。在

您可以按原样使用它,但它依赖于另一个库RXPromise。在

使用分块传输编码的解决方法提示:

如果事先知道流字节的大小,则可以显式设置内容长度标头。这将导致NSURLConnection不使用分块传输编码。在

正如@Rob在注释中所述,NSURLSession的行为方式不同:如果流被设置为输入,则内容长度头将被删除,这将导致分块传输编码。在

相关问题 更多 >

    热门问题