Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 993
PhoenixS Avatar asked Jan 20 '12 17:01

PhoenixS


1 Answers

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.

like image 143
supermarin Avatar answered Sep 28 '22 13:09

supermarin