对轮询流使用轮询功能

对轮询流使用轮询功能

我正在尝试使用C中的poll函数来实现通信系统的客户端-服务器类型。流程如下:

  1. 主程序派生一个子进程

  2. 子进程调用exec函数执行some_binary

  3. 父级和子级交替发送消息,发送的每个消息都取决于收到的最后一条消息。

我尝试使用来实现此功能poll,但由于子进程缓冲其输出,导致我的poll超时调用而遇到问题。这是我的代码:

int main() {char *buffer = (char *) malloc(1000);int n;pid_t pid; /* pid of child process */int rpipe[2]; /* pipe used to read from child process */int wpipe[2]; /* pipe used to write to child process */pipe(rpipe);pipe(wpipe);pid = fork();if (pid == (pid_t) 0){
    /* child */

    dup2(wpipe[0], STDIN_FILENO);
    dup2(rpipe[1], STDOUT_FILENO);
    close(wpipe[0]); close(rpipe[0]);
    close(wpipe[1]); close(rpipe[1]);
    if (execl("./server", "./server", (char *) NULL) == -1)
    {
        fprintf(stderr, "exec failed\n");
        return EXIT_FAILURE;
    }       
    return EXIT_SUCCESS;}else{
    /* parent */

    /* close the other ends */
    close(wpipe[0]);
    close(rpipe[1]);

    /* 
      poll to check if write is good to go 
                This poll succeeds, write goes through
        */
    struct pollfd pfds[1];
    pfds[0].fd = wpipe[1];
    pfds[0].events = POLLIN | POLLOUT;
    int pres = poll(pfds, (nfds_t) 1, 1000);
    if (pres > 0)
    {
        if (pfds[0].revents & POLLOUT)
        {
            printf("Writing data...\n");
            write(wpipe[1], "hello\n", 6);
        }
    }

    /* 
        poll to check if there's something to read.
        This poll times out because the child buffers its stdout stream.
    */
    pfds[0].fd = rpipe[0];
    pfds[0].events = POLLIN | POLLOUT;
    pres = poll(pfds, (nfds_t) 1, 1000);
    if (pres > 0)
    {
        if (pfds[0].revents & POLLIN)
        {
            printf("Reading data...\n");
            int n = read(rpipe[0], buffer, 1000);
            buffer[n] = '\0';
            printf("child says:\n%s\n", buffer);
        }
    }

    kill(pid, SIGTERM);
    return EXIT_SUCCESS;}}

如何防止poll由于缓冲而导致呼叫超时?

编辑:

我希望程序即使在execed二进制文件位于外部时也能运行,即我无法控制代码-像unix命令,例如cator ls


慕莱坞森
浏览 580回答 3
3回答

幕布斯6054654

正如我在对上一个问题的相关回答中所回答的那样,您需要实现一个事件循环;顾名思义,它是循环的,因此您应该在父进程中进行编码:while (1) { // simplistic event loop!    int status=0;    if (waitpid(pid, &status, WNOHANG) == pid)       { // clean up, child process has ended         handle_process_end(status);         break;       };    struct pollpfd pfd[2];    memset (&pfd, 0, sizeof(pfd)); // probably useless but dont harm    pfd[0].fd = rpipe[0];    pfd[0].events = POLL_IN;    pfd[1].fd = wpipe[1];    pfd[0].event = POLL_OUT;    #define DELAY 5000 /* 5 seconds */    if (poll(pfd, 2, DELAY)>0) {       if (pfd[0].revents & POLL_IN) {          /* read something from rpipe[0]; detect end of file;              you probably need to do some buffering, because you may              e.g. read some partial line chunk written by the child, and              you could only handle full lines. */       };       if (pfd[1].revents & POLL_OUT) {          /* write something on wpipe[1] */       };    }    fflush(NULL);} /* end while(1) */您无法预测管道的可读或可写顺序,并且这种情况可能会发生很多次。当然,涉及很多缓冲(在父进程中),我将详细信息留给您。...您对子进程中的缓冲没有影响(某些程序检测到它们的输出是否为isatty)。上面类似的事件轮询循环为您避免死锁情况,因为子进程的stdout管道已满,子进程被阻塞,而父进程由于管道已满而被阻止写入(写入子stdin管道)父进程:循环时,只要轮询某些数据在输入管道上即可读取(即子进程的stdout),便会读取数据,并且一旦轮询输出管道可写(即未满)就可以写入一些数据。您无法预先预测这些事件“孩子的输出可被父母读取”和“孩子的输入可被父母写入”的顺序。我建议阅读高级Linux编程,其中有几章解释了这些问题!顺便说一句,我的简单事件循环有点不对:如果子进程终止并且某些数据保留在其stdout管道中,则不会完成其读取。您可以waitpid在poll另外,不要期望单个write(来自子进程)进入管道会触发read父进程中的一个。换句话说,没有消息长度的概念。但是,POSIX知道PIPE_MAX...。请参阅其编写文档。也许您的缓冲区传递到read和write应该的PIPE_MAX大小。我重复一遍:您需要在事件循环内调用,poll并且很有可能poll会被调用多次(因为循环将重复多次!),并且将以不可预测(且不可重现)的顺序报告可读或可写的管道末端!程序的第一次运行可能报告“ rpipe[0]可读”,read从中可以得到324个字节,重复事件循环,poll说“ wpipe[1]可写”,可以write向其重复10个字节,然后重复事件循环,poll则表明“ rpipe[0]可读”,您read从中读取110个字节,重复事件循环,poll再次告诉“ rpipe[0]可读”,您read4096个字节,等等,等等...在相同环境中第二次运行同一程序将产生不同的事件,例如:poll说“ wpipe[1]可写”,您write对它写了1000个字节,重复循环,poll说“ rpipe[0]可读”等...注意:您的问题不是子程序(“客户端”)中的缓冲,我们认为您无法更改。因此,重要的不是其中的缓冲数据,而是真正的输入和输出(这是父进程唯一可以观察到的内容;内部子缓冲区与父进程无关),即子程序能够执行的数据真正 读取(2)和写入(2)。并且如果通过pipe(7),则此类数据将在父进程中变为poll(2)-(并且您的父进程可以在或之后的更新字段中read或write其中的一部分)。顺便说一句,如果您确实编码了孩子,别忘了打电话给POLL_INPOLL_OUTreventspollfflush 在里面合适的地方。

紫衣仙女

您的代码中似乎有两个问题。默认情况下,“ stdout”是缓冲的,因此服务器应显式刷新它:printf("I received %s\n", buffer);fflush(stdout);并且POLLOUT在尝试读取时主程序不应注册(但您可能想要注册POLLERR):pfds[0].fd = rpipe[0];pfds[0].events = POLLIN | POLLERR;通过这些修改,您可以获得预期的输出:$ ./main写入数据...读取数据...孩子说:我打招呼了通常,您还应该检查的返回值poll(),并在必要时重复调用(例如,在系统调用中断或超时的情况下)。

SMILET

从您的问题中我感觉到您拥有服务器代码,因此可以对其进行修改。即使使用“ cat”,如果您按照pfds[0].events = POLLIN | POLLERR我的建议进行设置,它似乎也可以工作。但是,如果外部程序对其输出进行缓冲(因此不会写入stdout),那么我认为您无法做任何事情。
打开App,查看更多内容
随时随地看视频慕课网APP