首页 > 解决方案 > 强制刷新或读取未刷新的输出

问题描述

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

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

TL; DR:该过程挂起,因为stdoutindc没有刷新或者这就是我认为已经找到的。无论如何,我如何阅读它或在每次写入后强制刷新?

我发现的内容在下面评论。

#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”命令并将它得到的所有内容输出到文件中(所以我可以调试它;我不知道其他方法)。如果有用,我可以在这里添加。

我发现对fflush(stdout)in (my)的调用会dc恢复前端,因为对fgetsfinally 的调用会返回。

我无法改变dc自己。怎么能不挂fgets

我的一些想法:

我正在寻找建议或一种简单的方法来避免它们。

我尝试过了:

标签: clinuxstdoutbuffered

解决方案


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

dup2(pfdin[1], STDERR_FILENO);

在子叉代码中。


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


另一种选择是在主循环中使用 poll 同时从 stdin 和 dc 读取。有了这个,你不需要奇怪的“发送 h 命令来触发错误”机制来确定 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可能是错误或挂断而不是POLLERR正常输入 - 所有这些都是非零值,因此仅针对非零进行测试是合理的)。POLLHUPPOLLIN


推荐阅读