Getting data from NSTask in real-time using notifications doesn't work
Asked Answered
D

3

9

I got a Cocoa command-line program in which I try to run NSTask program (tshark to monitor network) and get data from it in real-time. So I make a NSFileHandle , call waitForDataInBackgroundAndNotify to send notifications and then register my help class to Notification center to process the data, but not a single notification is sent to my help class.

Does anybody have an idea of what could be wrong?

Thanks in advance

Here is my code:

#import <Foundation/Foundation.h>
#import <string>
#import <iostream>

@interface toff : NSObject {}
-(void) process:(NSNotification*)notification;
@end

@implementation toff
-(void) process:(NSNotification*)notification{
    printf("Packet caught!\n");
}
@end

int main (int argc, const char * argv[]){
    @autoreleasepool {
        NSTask* tshark = [[NSTask alloc] init];
        NSPipe* p = [NSPipe pipe];
        NSFileHandle* read = [p fileHandleForReading];
        toff* t1 = [[toff alloc] init];
        NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];

        [read waitForDataInBackgroundAndNotify];
        [nc addObserver:t1 selector:@selector(process:) name:nil object:nil];

        printf("Type 'stop' to stop monitoring network traffic.\n");
        [tshark setLaunchPath:@"/usr/local/bin/tshark"];
        [tshark setStandardOutput:p];
        [tshark launch];

        while(1){
            std::string buffer;
            getline(std::cin, buffer);
            if(buffer.empty()) continue;
            else if(buffer.compare("stop") == 0){
                [tshark interrupt];
                break;
            }
        }

        //NSData* dataRead = [read readDataToEndOfFile];
        //NSLog(@"Data: %@", dataRead);
        //NSString* stringRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding];
        //NSLog(@"Output: %@", stringRead);

    }
    return 0;
}

EDIT: When I uncomment commented section of code and delete all that notification stuff, all desired data are extracted from file handle after task finish.

I was also wondering, if problem can't be in fact, that my program is 'Command line tool' so I am not sure if it has run loop - as Apple documentation says is needed (in waitForDataInBackgroundAndNotify message of NSFileHandle):

You must call this method from a thread that has an active run loop.

Dollydolman answered 20/1, 2012 at 17:57 Comment(5)
You really should make that addObserver:selector:name:object: message more specific. You're currently signing up for any notification, not just the NSFileHandleReadCompletionNotification notification.Krissie
The run loop is implicitly created when needed, so you can safely assume “it has [a] run loop”, but you do need to run it. An application would run the run loop for you, so all you would need to do is return, but in a command-line tool, you must run the run loop yourself. I recommend using NSFileHandle to read from standard input as well, and doing what I said in my answer to run the run loop until the task exits.Krissie
@PeterHosey Good idea - thanks.I tried to do this but didn't work. So I stepped back to simple, omitted stopping and came up with this (I also noticed that data passed by NSFileHandle are somehow buffered - don't know how to get rid of it) After start, it waits a while and then writes 'Packet caught' once - then nothing.If I uncomment 1 then it waits and then write 'Packet caught' unlimited times.And finally when I try to extract data and uncomment 2-5 it waits a while, writes lines 2 & 3 and then freezes.No crash, no error, just don't extract the data.Dollydolman
@user1023979 have you found the solution to this? Im having the same issue. No notifications is being sent.Mingo
@user76859403 My Mac is not working anymore so I can't try the solution suggested by mneorr. Maybe it works - try it yourself and let us know :-)Dollydolman
B
10

There's a new API since 10.7, so you can avoid using NSNotifications.

task.standardOutput = [NSPipe pipe];
[[task.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {
    NSData *data = [file availableData]; // this will read to EOF, so call only once
    NSLog(@"Task output! %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    // if you're collecting the whole output of a task, you may store it on a property
    [self.taskOutput appendData:data];
}];

Probably you want to repeat the same above for task.standardError.

IMPORTANT:

When your task terminates, you have to set readabilityHandler block to nil; otherwise, you'll encounter high CPU usage, as the reading will never stop.

[task setTerminationHandler:^(NSTask *task) {

    // do your stuff on completion

    [task.standardOutput fileHandleForReading].readabilityHandler = nil;
    [task.standardError fileHandleForReading].readabilityHandler = nil;
}];

This is all asynchronous (and you should do it async), so your method should have a ^completion block.

Briton answered 29/4, 2013 at 8:47 Comment(0)
K
1

Your program finishes immediately after launching the task. You need to tell the task to wait until exit. That will run the run loop, waiting for the child process to exit and coincidentally enabling the file handle to monitor the pipe. When the task does exit, the method will return and you can proceed to the end of main.

Krissie answered 20/1, 2012 at 21:38 Comment(2)
I should post whole the code. I edited my answer - now you can see, that I am giving the user possibility to manually stop the task from my program. I think that if I'd call waitUntilExit it wouldn't be possible. I also tried to extract data from file handler after stopping the task (commented section) and it worked perfectly.Dollydolman
And when I say 'I edited my answer' I mean 'I edited my question' - sorryDollydolman
D
0

Have a look at asynctask.m, sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask (there may be room for improvement though).

Dogy answered 4/2, 2012 at 15:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.