2014-04-07 35 views
10

我剛剛開始使用iOS上的套接字編程,並且我正在努力確定使用NSStreamEventHasSpaceAvailable事件處理NSOutputStreams使用NSOutputStream通過套接字發送數據的正確方法

在一方面,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 ...

回答

13

您可以隨時寫入流,但對於網絡流,-write:maxLength:只返回,直到至少有一個字節已寫入套接字寫入緩衝區。因此,如果套接字寫緩衝區已滿(例如因爲連接的另一端沒有足夠快地讀取數據),則將當前線程。如果您從主線程寫入,則會阻止用戶界面 。

NSStreamEventHasSpaceAvailable事件發送信號時,您可以寫入流 沒有阻塞。僅響應該事件而寫入可避免當前線程 以及可能的用戶界面被阻止。

或者,您可以從單獨的「寫入器線程」寫入網絡流。

12

看到@ MartinR的回答後,我重新閱讀了Apple Docs,並在NSRunLoop活動上做了一些閱讀。解決方案並不像我第一次想到的那樣微不足道,而且需要一些額外的緩衝。

結論

雖然雷Wenderlich例子有效,那它就不是最佳 - 由@MartinR指出,如果在傳出TCP窗口無房,調用write:maxLength將阻止。 Ray Wenderlich的例子確實起作用的原因是因爲發送的消息很少且不經常發生,並且給出了無差錯和大帶寬的互聯網連接,它「很可能」會工作。當你開始處理(很多)更多數據(更多)的數據時,這些write:maxLength:調用可能會開始阻止,並且應用程序將開始失速。

對於NSStreamEventHasSpaceAvailable事件,蘋果的文檔有以下建議:如果委託收到一個NSStreamEventHasSpaceAvailable事件並沒有寫任何東西到流

,它不接收從運行進一步的空間可用事件直到NSOutputStream對象接收更多的字節。 ......您可以讓委託在接收到NSStreamEventHasSpaceAvailable事件時不寫入流時設置一個標誌。後來,當你的程序有更多的字節要寫入時,它可以檢查這個標誌,如果設置了,直接寫入輸出流實例。

因此只有「保證安全」調用write:maxLength:在兩種情況下:

  1. 回調(在收到NSStreamEventHasSpaceAvailable事件)內。
  2. 只有當我們已經收到NSStreamEventHasSpaceAvailable,但在回調本身內部選擇不呼叫write:maxLength:(例如我們沒有實際寫入的數據)時,回調之外。

對於方案(2),我們將不會再收到回調,直到write:maxLength實際上是直接調用 - 蘋果建議設置委託回調內的標誌(見上文)來表示,當我們被允許這樣做。

我的解決方案是使用額外的緩衝水平 - 添加一個NSMutableArray作爲數據隊列。我寫數據到套接字代碼看起來像這樣(的意見和錯誤檢查不再贅述,在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; 
     } 
    } 
} 
+0

感謝您的洞察力,蜉蝣,看起來像我和你一樣有困惑。再次感謝! – Patricia

+0

請注意,bytesWritten可能是負數,表示出現錯誤,因此如果出現錯誤,上述代碼將會卡住,因爲currentDataOffset wouldnt匹配...編輯建議 –

+0

您能否幫助我瞭解接收器如何知道它是否只接收另一個大塊的數據或數據作爲一個整體?也許我的緩衝區容量是2字節,我一次只能寫兩個ASCII字符。接收者如何告訴我是否發送「do」或「dope」字樣? –

相關問題