【问题标题】:Correct way to send data through a socket with NSOutputStream使用 NSOutputStream 通过套接字发送数据的正确方法
【发布时间】:2014-12-04 02:59:51
【问题描述】:

我刚刚开始在 iOS 上进行套接字编程,我正在努力确定 NSStreamEventHasSpaceAvailable 事件对 NSOutputStreams 的使用。

一方面,Apple's official documentation (Listing 2) 表明,在-stream:handleEvent: 委托方法中,数据应该使用-write:maxLength: 消息写入输出缓冲区,每当收到NSStreamEventHasSpaceAvailable 事件时,从缓冲区不断传递数据.

另一方面,this tutorial from Ray Wenderlichthis iOS TCP socket example on GitHub 完全忽略了NSStreamEventHasSpaceAvailable 事件,只要他们需要,就继续前进,-write:maxLength: 到缓冲区(甚至忽略-hasSpaceAvailable)。

第三,this example code 似乎可以做到两者...

因此,我的问题是,将数据写入连接到套接字的NSOutputStream 的正确方法是什么?如果NSStreamEventHasSpaceAvailable 事件代码可以(显然)被忽略,那么它有什么用呢?在我看来,要么发生了非常幸运的 UB(在示例 2 和 3 中),要么有几种方法可以通过基于套接字的NSOutputStream...发送数据...

【问题讨论】:

    标签: ios cocoa-touch sockets


    【解决方案1】:

    您可以随时写入流,但对于网络流,-write:maxLength: 仅在至少一个字节被写入套接字写入缓冲区之前返回。因此,如果套接字写入缓冲区已满(例如,因为连接的另一端没有足够快地读取数据), 这将阻塞当前线程。如果你从主线程写,这将阻塞 用户界面。

    NSStreamEventHasSpaceAvailable 事件在您可以写入流时发出信号 没有阻塞。仅在响应该事件时写入可避免当前线程 并且可能用户界面被阻止。

    或者,您可以从单独的“写入器线程”写入网络流。

    【讨论】:

      【解决方案2】:

      看到@MartinR 的回答后,我重新阅读了Apple Docs 并阅读了NSRunLoop 事件。解决方案并不像我最初想象的那么简单,需要一些额外的缓冲。

      结论

      虽然 Ray Wenderlich 示例有效,但它并不是最优的 - 正如 @MartinR 所指出的,如果传出 TCP 窗口中没有空间,则对 write:maxLength 的调用将阻塞。 Ray Wenderlich 的示例确实有效的原因是因为发送的消息很小且不频繁,并且在无错误和大带宽的互联网连接的情况下,它“可能”会有效。但是,当您开始处理(更多)更频繁(更多)发送的数据时,write:maxLength: 调用可能会开始阻塞,并且应用程序将开始停止...

      对于NSStreamEventHasSpaceAvailable 事件,Apple 的文档有以下建议:

      如果委托接收到 NSStreamEventHasSpaceAvailable 事件并且没有向流中写入任何内容,则它不会从运行循环接收更多可用空间事件,直到 NSOutputStream 对象接收到更多字节。 ... ...您可以让委托在接收到 NSStreamEventHasSpaceAvailable 事件后不写入流时设置一个标志。稍后,当您的程序有更多字节要写入时,它可以检查此标志,如果设置,则直接写入输出流实例。

      因此,只有在两种情况下调用write:maxLength: 才能“保证安全”:

      1. 在回调内部(收到NSStreamEventHasSpaceAvailable 事件时)。
      2. 当且仅当我们已经收到 NSStreamEventHasSpaceAvailable 但选择不在回调内部调用 write:maxLength:(例如,我们没有实际写入的数据)时,在回调之外。

      对于场景 (2),在实际直接调用 write:maxLength 之前,我们不会再次收到回调 - Apple 建议在委托回调中设置一个标志(见上文)以指示我们何时可以这样做。

      我的解决方案是使用额外级别的缓冲 - 添加 NSMutableArray 作为数据队列。我将数据写入套接字的代码如下所示(为简洁起见,省略了 cmets 和错误检查,currentDataOffset 变量表示我们发送了多少“当前”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;
              }
          }
      }
      

      【讨论】:

      • 感谢您的洞察力,蜉蝣,看来我和你有同样程度的困惑。再次感谢!
      • 注意bytesWritten可能为负数,表示有错误,因此如果出现错误上述代码会卡住,因为currentDataOffset不匹配...编辑建议
      • 你能帮我理解接收者是如何知道它接收到的是另一块数据还是整个数据?也许我的缓冲区容量为 2 字节,我一次只能写两个 ASCII 字符。接收者如何判断我是否向其发送“do”或“dope”字样?
      • @purrrminator 您必须定义一个可以在每一端正确解析的通信协议。这段代码只是关于发送数据,而不是解释它的含义。
      • 太棒了!实际上,1个问题解决了2个问题:1)传输数据的队列实现2)传输数据本身。非常感谢!
      猜你喜欢
      • 2013-01-23
      • 1970-01-01
      • 2019-12-28
      • 1970-01-01
      • 1970-01-01
      • 2023-03-05
      • 2016-12-29
      • 2016-04-06
      • 1970-01-01
      相关资源
      最近更新 更多