简体   繁体   English

NSOutputStream完成后关闭连接

[英]Close connection when NSOutputStream has finished

How can a connection be closed when the NSOutputStream has finished sending data? NSOutputStream完成发送数据后,如何关闭连接?

After searching around i have found that the event NSStreamEventEndEncountered is only called if the server drops the connection. 搜索后,我发现仅当服务器断开连接时才调用NSStreamEventEndEncountered事件。 not if the OutputStream has finished the data to send. 如果OutputStream已经完成要发送的数据,则为false。

StreamStatus is Always returning 0 (connection closed) or 2 (connection open) but never 4 (writing data). StreamStatus始终返回0(关闭连接)或2(打开连接),但永不返回4(写入数据)。

since both methods mentioned above are not telling me enough about the write process i am not able to find a way do determine if the Stream is still writing or if it has finished and i can close the connection now. 由于上述两种方法都没有告诉我有关写入过程的足够信息,因此我无法找到一种方法来确定Stream是否仍在写入或是否已完成,并且现在可以关闭连接。

After 5 days of googleling and trying i am totally out of ideas... Any help appreciated. 经过5天的谷歌搜索和尝试,我完全失去了主意...感谢任何帮助。 Thanks 谢谢

EDIT ADDED CODE AS REQUESTED: 按要求编辑附加代码:

- (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;

}

}

There can be two interpretations to your question. 您的问题可能有两种解释。 If what you are asking is "I have a NSOutputStream and I'm finished writing to it how do I signal this?" 如果您要问的是“我有一个NSOutputStream,并且已经写完了,我该如何发出信号呢?” then the answer is as simple as call the close method on it. 那么答案就像在其上调用close方法一样简单。

Alternately, If what you are really saying is "I have a NSInputStream and I want to know when I've reached the end-of-stream" then you can look at hasBytesAvailable or streamStatus == NSStreamStatusAtEnd . 或者,如果您真正要说的是“我有一个NSInputStream,并且想知道何时到达流末尾”,那么您可以查看hasBytesAvailablestreamStatus == NSStreamStatusAtEnd

For your information, to actually get the status NSStreamStatusWriting you would need to be calling the streamStatus method from another thread while this thread is calling write:maxLength: . 为了您的信息,要真正获取状态NSStreamStatusWriting您需要在另一个线程调用write:maxLength:同时从另一个线程调用streamStatus方法。

--- Edit: Code Suggestion ---编辑:代码建议

The reason you would never get notified is that an output stream is never finished (unless it's a fixed size stream, which an FTP stream is not). 您永远不会收到通知的原因是输出流永远不会完成(除非它是固定大小的流,而FTP流却没有)。 It's the input stream that gets "finished" at which point you can close your output stream. 输入流将“完成”,此时您可以关闭输出流。 That's the answer to your original question. 那就是你原来的问题的答案。

As a further suggestion, I would skip run loop scheduling and the "event processing" except for handling errors on the output stream. 作为进一步的建议,除了处理输出流中的错误外,我将跳过运行循环调度和“事件处理”。 Then I would put the read/write code into a NSOperation subclass and send it off into a NSOperationQueue. 然后,将读/写代码放入NSOperation子类中,并将其发送到NSOperationQueue中。 By keeping a reference to the NSOperations in that queue you would be able to cancel them easily and even show a progress bar by adding a percentComplete property. 通过在该队列中保留对NSOperations的引用,您可以轻松地取消它们,甚至可以通过添加percentComplete属性来显示进度栏。 I've tested the code below and it works. 我已经测试了下面的代码,并且可以正常工作。 Replace my memory output stream with your FTP output stream. 用您的FTP输出流替换我的内存输出流。 You will notice that I have skipped the validations, which you should keep of course. 您会注意到我已经跳过了验证,您当然应该保留这些验证。 They should probably be done outside the NSOperation to make it easier to query the user. 它们可能应该在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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM