2013-04-28 46 views
8

當NSOutputStream完成發送數據時,如何關閉連接?當NSOutputStream完成時關閉連接

周圍搜索後,我發現,如果服務器斷開連接的情況下NSStreamEventEndEncountered只調用。如果OutputStream已完成要發送的數據,則不會。

StreamStatus總是返回0(連接關閉)或2(連接開放),但從來沒有4(寫入數據)。

,因爲上述兩種方法都沒有告訴我有足夠的瞭解寫入過程我不能夠找到一種方法做決定如果流還在寫,或者如果它已經完成了,我現在可以關閉連接。

後googleling和努力我完全沒了主意5天...任何幫助表示讚賞。由於

編輯添加的代碼的要求:

- (void)startSend:(NSString *)filePath 

{ 

BOOL     success; 

NSURL *     url; 



assert(filePath != nil); 

assert([[NSFileManager defaultManager] fileExistsAtPath:filePath]); 

assert([filePath.pathExtension isEqual:@"png"] || [filePath.pathExtension isEqual:@"jpg"]); 



assert(self.networkStream == nil);  // don't tap send twice in a row! 

assert(self.fileStream == nil);   // ditto 



// First get and check the URL. 

... 
.... 
..... 


// If the URL is bogus, let the user know. Otherwise kick off the connection. 

... 
.... 
..... 


if (! success) { 

    self.statusLabel.text = @"Invalid URL"; 

} else { 



    // Open a stream for the file we're going to send. We do not open this stream; 

    // NSURLConnection will do it for us. 



    self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath]; 

    assert(self.fileStream != nil); 



    [self.fileStream open]; 



    // Open a CFFTPStream for the URL. 



    self.networkStream = CFBridgingRelease(

     CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url) 

    ); 

    assert(self.networkStream != nil); 



    if ([self.usernameText.text length] != 0) { 

     success = [self.networkStream setProperty:self.usernameText.text forKey:(id)kCFStreamPropertyFTPUserName]; 

     assert(success); 

     success = [self.networkStream setProperty:self.passwordText.text forKey:(id)kCFStreamPropertyFTPPassword]; 

     assert(success); 

    } 



    self.networkStream.delegate = self; 

    [self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

    ///////******** LINE ADDED BY ME TO DISONNECT FROM FTP AFTER CLOSING CONNECTION *********//////////// 

    [self.networkStream setProperty:(id)kCFBooleanFalse forKey:(id)kCFStreamPropertyFTPAttemptPersistentConnection]; 

    ///////******** END LINE ADDED BY ME *********//////////// 

    [self.networkStream open]; 



    // Tell the UI we're sending. 



    [self sendDidStart]; 

} 

} 



- (void)stopSendWithStatus:(NSString *)statusString 

{ 

if (self.networkStream != nil) { 

    [self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

    self.networkStream.delegate = nil; 

    [self.networkStream close]; 

    self.networkStream = nil; 

} 

if (self.fileStream != nil) { 

    [self.fileStream close]; 

    self.fileStream = nil; 

} 

[self sendDidStopWithStatus:statusString]; 

} 



- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode 

// An NSStream delegate callback that's called when events happen on our 

// network stream. 

{ 

#pragma unused(aStream) 

assert(aStream == self.networkStream); 



switch (eventCode) { 

    case NSStreamEventOpenCompleted: { 

     [self updateStatus:@"Opened connection"]; 

    } break; 

    case NSStreamEventHasBytesAvailable: { 

     assert(NO);  // should never happen for the output stream 

    } break; 

    case NSStreamEventHasSpaceAvailable: { 

     [self updateStatus:@"Sending"]; 



     // If we don't have any data buffered, go read the next chunk of data. 



     if (self.bufferOffset == self.bufferLimit) { 

      NSInteger bytesRead; 



      bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize]; 



      if (bytesRead == -1) { 

       [self stopSendWithStatus:@"File read error"]; 

      } else if (bytesRead == 0) { 

       [self stopSendWithStatus:nil]; 

      } else { 

       self.bufferOffset = 0; 

       self.bufferLimit = bytesRead; 

      } 

     } 



     // If we're not out of data completely, send the next chunk. 



     if (self.bufferOffset != self.bufferLimit) { 

      NSInteger bytesWritten; 

      bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset]; 

      assert(bytesWritten != 0); 

      if (bytesWritten == -1) { 

       [self stopSendWithStatus:@"Network write error"]; 

      } else { 

       self.bufferOffset += bytesWritten; 

      } 

     } 

    } break; 

    case NSStreamEventErrorOccurred: { 

     [self stopSendWithStatus:@"Stream open error"]; 

    } break; 

    case NSStreamEventEndEncountered: { 

     // FOR WHATEVER REASON THIS IS NEVER CALLED!!!! 

    } break; 

    default: { 

     assert(NO); 

    } break; 

} 

} 

回答

4

可以有兩種解釋你的問題。如果你問的是「我有一個NSOutputStream,並且我已經寫完了,我該如何發信號?」那麼答案就像調用close方法一樣簡單。另外,如果你真正說的是「我有一個NSInputStream,我想知道什麼時候我已經到達流結束」,那麼你可以看看hasBytesAvailablestreamStatus == NSStreamStatusAtEnd

對於您的信息,要實際獲得狀態NSStreamStatusWriting,您需要在此線程調用write:maxLength:時從另一個線程調用streamStatus方法。

---編輯:代碼建議

你永遠不會得到通知的原因是輸出流從未完成(除非它是一個固定大小的數據流,其中FTP流不是)。這是輸入流被「完成」的時刻,您可以關閉輸出流。這是你原來的問題的答案。

作爲進一步的建議,我將跳過運行的循環調度和「事件處理」除了在輸出流上的錯誤處理。然後,我會將讀/寫代碼放入NSOperation子類中,並將其發送到NSOperationQueue中。通過在該隊列中保留對NSOperations的引用,您可以輕鬆取消它們,甚至可以通過添加percentComplete屬性來顯示進度條。我測試了下面的代碼,它的工作原理。用您的FTP輸出流替換我的記憶輸出流。你會注意到我跳過了驗證,你當然應該保留這些驗證。他們應該在NSOperation之外完成,以便查詢用戶。

@interface NSSendFileOperation : NSOperation<NSStreamDelegate> { 

    NSInputStream *_inputStream; 
    NSOutputStream *_outputStream; 

    uint8_t *_buffer; 
} 

@property (copy) NSString* sourceFilePath; 
@property (copy) NSString* targetFilePath; 
@property (copy) NSString* username; 
@property (copy) NSString* password; 

@end 


@implementation NSSendFileOperation 

- (void) main 
{ 
    static int kBufferSize = 4096; 

    _inputStream = [NSInputStream inputStreamWithFileAtPath:self.sourceFilePath]; 
    _outputStream = [NSOutputStream outputStreamToMemory]; 
    _outputStream.delegate = self; 

    [_inputStream open]; 
    [_outputStream open]; 

    _buffer = calloc(1, kBufferSize); 

    while (_inputStream.hasBytesAvailable) { 
     NSInteger bytesRead = [_inputStream read:_buffer maxLength:kBufferSize]; 
     if (bytesRead > 0) { 
      [_outputStream write:_buffer maxLength:bytesRead]; 
      NSLog(@"Wrote %ld bytes to output stream",bytesRead); 
     } 
    } 

    NSData *outputData = [_outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; 
    NSLog(@"Wrote a total of %lu bytes to output stream.", outputData.length); 

    free(_buffer); 
    _buffer = NULL; 

    [_outputStream close]; 
    [_inputStream close]; 
} 

- (void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode 
{ 
    // Handle output stream errors such as disconnections here 
} 

@end 


int main (int argc, const char * argv[]) 
{ 
    @autoreleasepool { 

     NSOperationQueue *sendQueue = [[NSOperationQueue alloc] init]; 

     NSSendFileOperation *sendOp = [[NSSendFileOperation alloc] init]; 
     sendOp.username = @"test"; 
     sendOp.password = @"test"; 
     sendOp.sourceFilePath = @"/Users/eric/bin/data/english-words.txt"; 
     sendOp.targetFilePath = @"/Users/eric/Desktop/english-words.txt"; 

     [sendQueue addOperation:sendOp]; 
     [sendQueue waitUntilAllOperationsAreFinished]; 
    } 
    return 0; 
} 
+0

你好!我的情況是這樣的:輸入流讀取文件,輸出流將其寫入FTP服務器。當輸入流完成寫入緩衝區時,我關閉了連接。但是這造成了一個問題,因爲當我關閉連接時,輸出流並未將整個緩衝區寫入到ftp。所以我需要知道輸出流何時寫完,以便我可以在不切斷文件結尾的情況下關閉連接。謝謝! – sharkyenergy 2013-04-29 06:01:05

+0

然後,問題不在於關閉輸入流,而在於您釋放了與之一起讀取數據的緩衝區(輸入流和緩衝區是兩個不同的對象)。如果您已完成讀取輸入,則您已將所有數據複製到緩衝區,因此可以關閉輸入流。只有在完成寫入輸出流後才能釋放緩衝區。 – aLevelOfIndirection 2013-04-29 06:07:14

+1

其關閉產生問題的輸出流。我不得不禁用「attemptPersistentConnection」,因爲我無法連接打開。所以在我上傳文件後,我必須關閉輸出流。問題是我不知道輸出流何時完成其工作。我只知道什麼時候輸入流完成.. – sharkyenergy 2013-04-29 08:01:40