强制刷新或读取未刷新的输出

Force flush or read unflushed output

这是工具的前端 dc;这个想法是键入一个中缀表达式 (2 + 3),将其映射到相应的后缀符号 (2 3 +) 并将其发送到 dc。这就是 bc 所做的。

我正在使用管道执行此操作,但前端挂起等待输出。
这是代码;我会在下面继续评论它。 dc 中的 "h" 命令未实现,因此我正在寻找 dc.

输出的结尾

TL;DR:进程挂起,因为 dc 中的 stdout 没有刷新,或者这就是我认为已经发现的。我怎样才能不顾一切地读取它或在每次写入后强制刷新?

我的发现在下面评论。

#define DC_EOF_RCV "dc: 'h' (0150) unimplemented"
#define DC_EOF_SND "h\n"

static FILE *sndfp, *rcvfp;

int dcinvoke() {
    int pfdout[2], pfdin[2];
    pid_t pid;

    pipe(pfdout);
    pipe(pfdin);
    switch (pid = fork()) {
        case -1: exit(1);
        case 0:
            dup2(pfdout[0], STDIN_FILENO);
            dup2(pfdin[1], STDOUT_FILENO);
            close(pfdout[0]);
            close(pfdout[1]);
            close(pfdin[0]);
            close(pfdin[1]);
            execlp("dc", "dc", "-", NULL);
    }
    close(pfdout[0]);
    close(pfdin[1]);
    sndfp = fdopen(pfdout[1], "w");
    rcvfp = fdopen(pfdin[0], "r");
    return 1;
}

void dcsnd(const char *s) {
    fputs(s, sndfp);
    fflush(sndfp);
}

void dcrcv(char *buf, size_t max) {
    fgets(buf, max, rcvfp); // <<<<< HANGS HERE
}

int turnaround() {
    dcsnd(DC_EOF_SND); fflush(sndfp);
}

int rcvall() {
    char buf[256];
    turnaround();
    for (;;) {
        dcrcv(buf, sizeof(buf));
        if (! strcmp(buf, DC_EOF_RCV)) {
            break;
        }
        printf("%s", buf);
    }
    return 1;
}

int prompt(const char *msg, char *res, size_t resmax, int *eofp) {
    char *p;
    printf("\n%s ", msg);
    if (!fgets(res, resmax, stdin)) {
        *eofp = 1;
    } else {
        if (p = strrchr(res, '\n')) {
            *p = 0;
        }
        *eofp = 0;
    }
    return 1;
}

int main() {
    char buf[128], line[128];
    int eof;

    dcinvoke();
    dcsnd("2 3 +\n");
    dcsnd(DC_EOF_SND);
    rcvall();
    for (;;) {
        prompt("Expression", buf, sizeof(buf), &eof);
        if (eof) {
            break;
        }
        snprintf(line, sizeof(line), "%s\n", buf);
        dcsnd(line);
        rcvall();
    }
    dcsnd("q\n");
    return 0;
}

为了简化这个问题,我删除了错误检查。

前端挂在以下行:

fgets(buf, max, rcvfp);

因为我真的不知道如何找到问题,所以我写了自己的 dc 它除了正确响应 "h" 命令并将它得到的所有内容输出到一个文件之外什么都不做(所以我可以调试它;我不知道任何其他方式)。有用的话我可以加进来

我发现在 (my) dc 中对 fflush(stdout) 的调用恢复了前端,而对 fgets 的调用最终 returns.

我无法改变 dc 本身。怎么挂不上fgets

我的一些想法:

我正在寻找避免这两种情况的建议或简单方法。

我试过:

dc 将错误消息发送到 stderr,而不是 stdout,因此您永远不会看到它们——它们会直接发送到终端(或您环境中连接到的任何 stderr)。为了能够在您的程序中读取它们,您还需要重定向 stderr。添加

dup2(pfdin[1], STDERR_FILENO);

在子分支代码中。


为了使您的代码实际工作,我还必须在 DC_EOF_RCV 字符串的末尾添加一个换行符(以匹配 dc 发送的内容),并从 main 中删除额外的 dcsnd(DC_EOF_SND); 调用,因为您永远不会在任何地方进行匹配的接收。即使这样,您仍然可以在写入 stdout 和 stderr 之间获得 dc 内的竞争条件,这可能导致输出行混合,导致响应与您的 DC_EOF_RCV 字符串不匹配。您可以通过在 turnaround 函数中添加一个小睡眠来避免这些。


另一种选择是在主循环中使用 poll 来同时读取 stdin 和 dc。有了这个,您就不需要奇怪的 "send h command to trigger an error" 机制来确定 dc 何时完成——您只需循环即可。您的主循环最终看起来像:

struct pollfd polling[2] = { { STDIN_FILENO, POLLIN, 0 }, { fileno(rcvfp), POLLIN, 0 } };
printf("Expression ");
for(;;) {
    if (poll(polling, 2, -1) < 0) {
        perror("poll");
        exit(1); }
    if (polling[0].revents) {
        /* stdin is ready */
        if (!fgets(buf, sizeof(buf), stdin))
            break;
        dcsnd(buf); }
    if (polling[1].revents) {
        /* dc has something for us */
        printf("\r          \r");   /* erase the previsouly printed prompt */
        dcrcv(buf, sizeof(buf));
        printf("%s\n", buf); }
    printf("Expression ");  /* new prompt */
}

这里有更多关于管理错误情况的详细信息(revents 可能是 POLLERRPOLLHUP 表示错误或挂断,而不是 POLLIN 表示正常输入——所有这些都是非零值,因此仅针对非零值进行测试是合理的)。

使用macOS 10.14.6 Mojave提供的dc命令,向程序发送2 3 +可以正常工作;它什么也没说,因为你没有要求它。

$ dc
2 3 +
p
5
q
$

p(打印)命令触发响应 5q 命令退出。

发送 h 收到混合响应:

$ dc
h
dc: 'h' (0150) unimplemented
q
$

响应是混合的,因为 dc:(和 space)转到标准错误,消息到标准输出:

$ dc >/dev/null
h
dc:
q
$

$ dc 2>/dev/null
h
'h' (0150) unimplemented
q
$

因此,我认为您的主要问题是您正在等待 dc 不会提供的响应。如果你想确保你得到一个打印的值,那么你需要添加一个p命令。它可以与 2 3 + 表达式在同一行或不同行。

$ dc
2 3 + p
5
2
3
+
p
5
q
$