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.

PhoenixS
  • 906
  • 1
  • 8
  • 22
  • 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. – Peter Hosey Jan 23 '12 at 12:13
  • 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. – Peter Hosey Jan 23 '12 at 12:17
  • @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](http://pastebin.com/qnhaUAjp) (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. – PhoenixS Jan 23 '12 at 19:50
  • @user1023979 have you found the solution to this? Im having the same issue. No notifications is being sent. – Just a coder May 22 '13 at 00:32
  • @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 :-) – PhoenixS May 27 '13 at 10:00

3 Answers3

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.

supermarin
  • 638
  • 6
  • 11
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.

Peter Hosey
  • 93,914
  • 14
  • 203
  • 366
  • 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. – PhoenixS Jan 21 '12 at 08:30
  • And when I say 'I edited my answer' I mean 'I edited my question' - sorry – PhoenixS Jan 21 '12 at 16:45
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).

vron
  • 1