我正在尝试做的事情
我的 Cocoa 应用程序需要运行一堆命令行程序。其中大部分是非交互式的,所以我用一些命令行参数启动它们,它们做他们的事情,输出一些东西并退出。其中一个程序是交互式的,因此它会向标准输出输出一些文本和提示,然后期待标准输入上的输入,并且这种情况一直持续到您向其发送退出命令。
什么有效
仅将大量数据转储到标准输出然后终止的非交互式程序相对来说是微不足道的:
NSPipe
为 stdout/stdin/stderr创建sNSTask
使用这些管道启动
那么,要么
- 让
NSFileHandle
管道的另一端读取所有数据,直到流结束,并在任务结束时一次性处理它
或者
- 从输出管道的另一端获取
-fileDescriptor
s 。NSFileHandle
- 将文件描述符设置为使用非阻塞模式
- 使用每个文件描述符创建一个 GCD 调度源
dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, ...
- 恢复调度源并使用它处理它向您抛出的数据
read()
- 继续进行,直到任务结束并且管道文件描述符报告 EOF(
read()
报告读取 0 字节)
什么不起作用
对于交互式工具,这两种方法都完全失效。显然我不能等到程序退出,因为它位于命令提示符下,除非我告诉它,否则永远不会退出。另一方面,NSPipe
缓冲数据,因此您以缓冲区大小的块接收它,除非 CLI 程序碰巧显式地刷新管道,而在我的情况下则没有。初始命令提示符比缓冲区大小小得多,所以我什么也没有收到,它就在那里。所以NSPipe
也是不行的。
经过一番研究,我确定我需要使用伪终端(pty) 来代替 NSPipe。不幸的是,除了让它正常工作之外,我什么都没有。
我试过的
我创建了一个像这样的 pty,而不是 stdout 管道:
struct termios termp;
bzero(&termp, sizeof(termp));
int res = openpty(&masterFD, &slaveFD, NULL, &termp, NULL);
这给了我两个文件描述符;我把它slaveFD
交给一个NSFileHandle
,它被传递给NSTask
stdout 或 stdout 和 stdin 。然后我尝试从主端进行通常的异步读取。
如果我在终端窗口中运行我正在控制的程序,它首先输出 2 行文本,一行 18 字节长,包括换行符,一个 22 字节长,命令提示符没有换行符。在这 40 个字节之后,它等待输入。
如果我只是将 pty 用于标准输出,我会从受控程序收到 18 个字节的输出(正好是一行,以换行符结尾),仅此而已。在最初的 18 个字节之后,一切都在那里,没有更多的事件 - GCD 事件源的处理程序不会被调用。
如果我还将 pty 用于标准输入,我通常会收到 19 个字节的输出(上述行加上下一行的一个字符),然后受控程序立即终止。如果我在尝试读取数据之前稍等片刻(或调度噪音会导致短暂的停顿),我实际上会在程序再次立即终止之前获得整个 40 个字节。
额外的死胡同
NSFileHandle
有一次我想知道我的异步读取代码是否有缺陷,所以我使用s 及其-readInBackgroundAndNotify
方法重新做了所有事情。这与使用 GCD 时的行为相同。(我最初选择 GCD 而不是NSFileHandle
API,因为在 中似乎没有任何异步写入支持NSFileHandle
)
问题
经过一天多的徒劳尝试,我已经到了这一点,我可以得到某种帮助。我正在尝试做的事情是否存在一些基本问题?为什么将标准输入连接到 pty 会终止程序?我没有关闭 pty 的主端,所以它不应该收到 EOF。撇开标准输入不谈,为什么我只能得到一行的输出?我在 pty 的文件描述符上执行 I/O 的方式有问题吗?我是否正确使用了主从端-控制过程中的主端,NSTask中的从端?
我没试过的
到目前为止,我只在管道和 pty 上执行了非阻塞(异步)I/O。我唯一能想到的是 pty 根本不支持这一点。(如果是这样,为什么会fcntl(fd, F_SETFL, O_NONBLOCK);
成功?)我可以尝试在后台线程上阻塞 I/O 并将消息发送到主线程。我希望避免不得不处理多线程,但考虑到所有这些 API 看起来有多糟糕,它不会比尝试另一种异步 I/O 排列更耗时。不过,我很想知道我到底做错了什么。