使用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:在两种情况下:

  1. 在callback中(收到NSStreamEventHasSpaceAvailable事件)。
  2. 在callback之外,当且仅当我们已经收到NSStreamEventHasSpaceAvailable但select不在callback本身中调用write:maxLength:例如,我们没有实际写入的数据)。

对于场景(2),直到write:maxLength我们才会再次收到callbackwrite:maxLength实际上是直接调用的 – 苹果公司build议在委托callback中设置一个标志(见上文)以指示何时允许我们执行此操作。

我的解决scheme是使用额外的缓冲级别 – 添加一个NSMutableArray作为数据队列。 我编写数据到套接字的代码看起来像这样(为简洁起见,省略了注释和错误检查, currentDataOffsetvariables指示我们发送了多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; } } }