使用NSOutputStream通过套接字发送数据的正确方法
我刚开始在iOS上的套接字编程,我正在努力确定使用NSStreamEventHasSpaceAvailable
事件的NSOutputStreams
。
一方面, Apple的官方文档(清单2)显示在-stream:handleEvent:
delegate方法中,应该使用-write:maxLength:
message将数据写入输出缓冲区,每当NSStreamEventHasSpaceAvailable
事件被收到。
另一方面, 来自Ray Wenderlich的这个教程和GitHub上的这个iOS TCP套接字例子完全忽略了NSStreamEventHasSpaceAvailable
事件,并且只要他们需要(甚至忽略-hasSpaceAvailable
),继续-write:maxLength:
到缓冲区。
第三,有这个例子代码似乎做两个 …
我的问题是,什么是正确的方式来处理写入数据到一个NSOutputStream
连接到套接字? 如果NSStreamEventHasSpaceAvailable
事件代码(显然)被忽略,那么它有什么用处? 在我看来,有非常幸运的UB发生(在例子2和3),或者有几种方式通过基于套接字的NSOutputStream
发送数据…
您可以随时写入stream,但是对于networkingstream, -write:maxLength:
只返回,直到至less有一个字节写入套接字写入缓冲区。 因此,如果套接字写缓冲区已满(例如,因为连接的另一端没有足够快地读取数据),则会阻塞当前线程。 如果从主线程写入,则会阻塞用户界面。
当您可以在不阻塞的情况下写入stream时,将NSStreamEventHasSpaceAvailable
事件。 只写响应该事件避免了当前线程和可能的用户界面被阻塞。
或者,您可以从单独的“写入程序线程”写入networkingstream。
看到@ MartinR的回答之后,我重新阅读了Apple Docs,并阅读了一些关于NSRunLoop
事件的文章。 解决scheme并不像我第一次想到的那样微不足道,而且需要一些额外的缓冲。
结论
当Ray Wenderlich示例工作时,它不是最优的 – 正如@MartinR所指出的那样,如果在传出的TCP窗口中没有空间, write:maxLength
的调用将被阻塞。 Ray Wenderlich的例子确实起作用的原因是因为发送的消息很less且不经常,并且给出了一个无差错和大带宽的互联网连接,它“可能”会工作。 当你开始处理大量(更多)的数据(更多)时, write:maxLength:
调用可能会开始阻塞,应用程序将开始停顿…
对于NSStreamEventHasSpaceAvailable
事件,Apple的文档有以下build议:
如果委托收到一个NSStreamEventHasSpaceAvailable事件,并且不向stream中写入任何内容,那么直到NSOutputStream对象接收到更多的字节,它才会从运行循环中收到更多的空间可用事件。 … …您可以让委托在接收到NSStreamEventHasSpaceAvailable事件时不写入stream时设置一个标志。 后来,当你的程序有更多的字节要写入时,它可以检查这个标志,如果设置,直接写入输出stream实例。
因此,只有“保证安全”才能调用write:maxLength:
在两种情况下:
- 在callback中(收到
NSStreamEventHasSpaceAvailable
事件)。 - 在callback之外,当且仅当我们已经收到
NSStreamEventHasSpaceAvailable
但select不在callback本身中调用write:maxLength:
例如,我们没有实际写入的数据)。
对于场景(2),直到write:maxLength
我们才会再次收到callbackwrite:maxLength
实际上是直接调用的 – 苹果公司build议在委托callback中设置一个标志(见上文)以指示何时允许我们执行此操作。
我的解决scheme是使用额外的缓冲级别 – 添加一个NSMutableArray
作为数据队列。 我编写数据到套接字的代码看起来像这样(为简洁起见,省略了注释和错误检查, currentDataOffset
variables指示我们发送了多less“当前” NSData
对象):
// Public interface for sending data. - (void)sendData:(NSData *)data { [_dataWriteQueue insertObject:data atIndex:0]; if (flag_canSendDirectly) [self _sendData]; } // NSStreamDelegate message - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { // ... case NSStreamEventHasSpaceAvailable: { [self _sendData]; break; } } // Private - (void)_sendData { flag_canSendDirectly = NO; NSData *data = [_dataWriteQueue lastObject]; if (data == nil) { flag_canSendDirectly = YES; return; } uint8_t *readBytes = (uint8_t *)[data bytes]; readBytes += currentDataOffset; NSUInteger dataLength = [data length]; NSUInteger lengthOfDataToWrite = (dataLength - currentDataOffset >= 1024) ? 1024 : (dataLength - currentDataOffset); NSInteger bytesWritten = [_outputStream write:readBytes maxLength:lengthOfDataToWrite]; currentDataOffset += bytesWritten; if (bytesWritten > 0) { self.currentDataOffset += bytesWritten; if (self.currentDataOffset == dataLength) { [self.dataWriteQueue removeLastObject]; self.currentDataOffset = 0; } } }