为了账号安全,请及时绑定邮箱和手机立即绑定

对轮询流使用轮询功能

对轮询流使用轮询功能

C
慕莱坞森 2019-09-26 15:24:54
对轮询流使用轮询功能我正在尝试使用C中的poll函数来实现通信系统的客户端-服务器类型。流程如下:主程序派生一个子进程子进程调用exec函数执行some_binary父级和子级交替发送消息,发送的每个消息都取决于收到的最后一条消息。我尝试使用来实现此功能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。
查看完整描述

3 回答

?
幕布斯6054654

TA贡献1876条经验 获得超7个赞

正如我在对上一个问题的相关回答中所回答的那样,您需要实现一个事件循环;顾名思义,它是循环的,因此您应该在父进程中进行编码:

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管道中,则不会完成其读取。您可以waitpidpoll

另外,不要期望单个write(来自子进程)进入管道会触发read父进程中的一个。换句话说,没有消息长度的概念。但是,POSIX知道PIPE_MAX...。请参阅其编写文档。也许您的缓冲区传递到readwrite应该的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)-(并且您的父进程可以在或之后的更新字段中readwrite其中的一部分)。顺便说一句,如果您确实编码了孩子,别忘了打电话给POLL_INPOLL_OUTreventspollfflush 在里面合适的地方。


查看完整回答
反对 回复 2019-09-26
?
紫衣仙女

TA贡献1839条经验 获得超15个赞

您的代码中似乎有两个问题。默认情况下,“ stdout”是缓冲的,因此服务器应显式刷新它:

printf("I received %s\n", buffer);fflush(stdout);

并且POLLOUT在尝试读取时主程序不应注册(但您可能想要注册POLLERR):

pfds[0].fd = rpipe[0];pfds[0].events = POLLIN | POLLERR;

通过这些修改,您可以获得预期的输出:

$ ./main写入数据...读取数据...孩子说:我打招呼了

通常,您还应该检查的返回值poll(),并在必要时重复调用(例如,在系统调用中断或超时的情况下)。


查看完整回答
反对 回复 2019-09-26
?
SMILET

TA贡献1796条经验 获得超4个赞

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

查看完整回答
反对 回复 2019-09-26
  • 3 回答
  • 0 关注
  • 579 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信