使用通知实时从NSTask获取数据无法正常工作

9
我有一个Cocoa命令行程序,我试图运行 NSTask 程序(tshark 来监视网络),并实时从中获取数据。所以我建立了一个 NSFileHandle,调用 waitForDataInBackgroundAndNotify 发送通知,然后注册我的帮助类到通知中心来处理数据,但是没有一条通知被发送到我的帮助类。
有人知道可能出了什么问题吗?
先感谢您的帮助。
这是我的代码:
#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;
}
编辑: 当我取消代码的注释并删除所有通知内容后,任务完成后从文件句柄提取了所有所需的数据。
我还在想,如果问题实际上不在于我的程序是“命令行工具”,因此我不确定它是否有运行循环-正如苹果文档中在NSFileHandle的waitForDataInBackgroundAndNotify消息中所说的那样需要的那样:

您必须从具有活动运行循环的线程中调用此方法。


你真的应该让addObserver:selector:name:object:这个消息更具体一些。目前你正在注册任何通知,而不仅仅是NSFileHandleReadCompletionNotification通知。 - Peter Hosey
运行循环会在需要时隐式创建,因此您可以安全地假设“它有[一个]运行循环”,但是您确实需要运行它。应用程序将为您运行运行循环,因此您只需要返回即可,但在命令行工具中,您必须自己运行运行循环。我建议使用NSFileHandle从标准输入读取,并按照我的答案中所说的运行运行循环,直到任务退出。 - Peter Hosey
@PeterHosey 好主意 - 谢谢。我尝试过这个,但没有成功。所以我退回到简单的方法,省略了停止,并想出了这个(我还注意到由NSFileHandle传递的数据在某种程度上被缓冲 - 不知道如何摆脱它)。启动后,它会等待一段时间,然后写入“Packet caught”一次 - 然后什么也不做。如果我取消注释1,那么它会等待一段时间,然后无限次地写入“Packet caught”。最后,当我尝试提取数据并取消注释_2-5_时,它会等待一段时间,写入第_2和3_行,然后冻结。没有崩溃,没有错误,只是无法提取数据。 - PhoenixS
@user1023979,你找到解决方法了吗?我也遇到了同样的问题。没有通知被发送。 - Just a coder
我的Mac不再工作,所以我无法尝试mneorr建议的解决方案。也许它有效-你可以自己尝试并告诉我们 :-) - PhoenixS
3个回答

10

自10.7起有一个新的API,因此您可以避免使用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];
}];

可能您希望对task.standardError同样执行以上操作。

重要提示:

当任务终止时,您必须将readabilityHandler块设置为nil;否则,由于读取永远不会停止,您将遇到高CPU使用率。

[task setTerminationHandler:^(NSTask *task) {

    // do your stuff on completion

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

这些操作都是异步进行的(你应该使用异步方式),因此你的方法应该有一个^完成块。


1

在启动任务后,您的程序立即结束。您需要告诉任务等待退出。这将运行运行循环,等待子进程退出并巧合地启用文件句柄来监视管道。当任务退出时,该方法将返回,然后您可以继续执行main的结尾。


我应该发布整个代码。我编辑了我的答案 - 现在你可以看到,我给用户手动停止我的程序中的任务的可能性。我认为如果我调用waitUntilExit,这是不可能的。我还尝试在停止任务后从文件处理程序中提取数据(注释部分),它完美地工作了。 - PhoenixS
当我说“我编辑了我的答案”时,我意思是“我编辑了我的问题”- 很抱歉。 - PhoenixS

0

看一下asynctask.m,这是一个示例代码,展示了如何使用NSTask实现异步标准输入、输出和错误流来处理数据(虽然可能还有改进的空间)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接