我刚开始使用iOS上的套接字编程,我正在努力确定NSStreamEventHasSpaceAvailable
事件对NSOutputStreams
的使用。
一方面,Apple's official documentation (Listing 2)表明在-stream:handleEvent:
委托方法中,应该使用-write:maxLength:
消息将数据写入输出缓冲区,每当从缓冲区连续传递数据时收到NSStreamEventHasSpaceAvailable
事件。
另一方面,this tutorial from Ray Wenderlich和this iOS TCP socket example on GitHub完全忽略了NSStreamEventHasSpaceAvailable
事件,只要他们需要,就可以继续-write:maxLength:
到缓冲区(甚至忽略{ {1}})。
第三,this example code似乎 ......
因此,我的问题是,处理将数据写入附加到套接字的-hasSpaceAvailable
的正确方法是什么?如果可以(显然)忽略NSOutputStream
事件代码,它有什么用?在我看来,有非常幸运的UB发生(在示例2和3中),或者有几种方式通过基于套接字的NSStreamEventHasSpaceAvailable
发送数据......
答案 0 :(得分:13)
您可以随时写入流,但对于网络流,-write:maxLength:
仅返回至少一个字节已写入套接字写入缓冲区。因此,如果套接字写缓冲区已满(例如,因为连接的另一端没有足够快地读取数据),
这将阻止当前线程。如果你从主线程写入,这将阻止
用户界面。
当您可以写入流时,会发出NSStreamEventHasSpaceAvailable
事件的信号
没有阻止。仅响应该事件而写入可避免当前线程
并且可能阻止了用户界面。
或者,您可以从单独的“编写器线程”写入网络流。
答案 1 :(得分:12)
在看到@ MartinR的回答之后,我重新阅读了Apple Docs,并对NSRunLoop
事件做了一些阅读。解决方案并不像我最初想的那么简单,需要一些额外的缓冲。
<强>结论强>
虽然Ray Wenderlich示例有效,但它不是最优的 - 正如@MartinR所指出的,如果传出TCP窗口中没有空间,对write:maxLength
的调用将会阻塞。 Ray Wenderlich的例子确实起作用的原因是因为发送的消息很小而且不常见,并且给定了无差错和大带宽的互联网连接,它可能会“#”;工作。然而,当您开始处理(更多)更大量的数据(更多)时,write:maxLength:
调用可能会开始阻止,应用程序将开始停止......
对于NSStreamEventHasSpaceAvailable
事件,Apple的文档提供了以下建议:
如果委托收到NSStreamEventHasSpaceAvailable事件并且没有向流写入任何内容,则它不会从运行循环接收更多空间可用事件,直到NSOutputStream对象接收到更多字节。 ... ...当接收到NSStreamEventHasSpaceAvailable事件时,如果委托不写入流,则可以让委托设置一个标志。稍后,当您的程序有更多字节要写时,它可以检查此标志,如果设置,则直接写入输出流实例。
因此,只保证安全。在两种情况下调用write:maxLength:
:
NSStreamEventHasSpaceAvailable
事件后)。NSStreamEventHasSpaceAvailable
但选择不在回调本身内调用write:maxLength:
时(例如我们没有实际写入的数据)。对于方案(2),我们将不会再次收到回调,直到实际直接调用write:maxLength
- Apple建议在委托回调中设置一个标志(见上文),以指示我们何时允许这样做。
我的解决方案是使用额外级别的缓冲 - 添加NSMutableArray
作为数据队列。我将数据写入套接字的代码如下所示(为简洁起见,省略了注释和错误检查,currentDataOffset
变量表示我们发送的当前&#39; 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;
}
}
}