使用轮询函数与缓冲stream
我试图在C中使用轮询函数来实现客户机 – 服务器types的通信系统。stream程如下:
- 主程序分叉了一个subprocess
- subprocess调用
exec
函数来执行some_binary
- 父母和孩子交替发送消息,发送的每个消息都取决于收到的最后一条消息。
我试图用poll
来实现这个function,但是由于subprocess缓冲它的输出而导致问题,导致我的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; } }
服务器代码很简单:
int main() { char *buffer = (char *) malloc(1000); while (scanf("%s", buffer) != EOF) { printf("I received %s\n", buffer); } return 0; }
如何防止由于缓冲而超时的poll
呼叫?
编辑:
即使exec
二进制文件是外部的,也就是说,我无法控制代码 – 就像是一个unix命令,例如cat
或ls
,我希望程序能够正常工作。
正如我在回答你先前提出的一个问题时,你需要实现一个事件循环 ; 顾名思义,它是循环的 ,所以你应该在父进程中编码:
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 eg 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) */
您无法预测pipe道可以读取或写入的顺序,这可能会发生多次。 当然,涉及到很多缓冲(在父进程中),我把细节留给你们……你对subprocess中的缓冲没有影响(一些程序检测到它们的输出是否是terminalisatty )。
像上面这样的事件轮询循环给你的是,避免由于其标准输出pipe道已满而导致subprocess被阻塞的情况,而由于pipe道已满而父节点被阻塞写入(到孩子的标准inputpipe道):循环中,只要有一些数据在inputpipe道(即subprocess的标准输出)上可读,就可以读取,并且只要输出pipe道被轮询为可写(即未满),就会写入一些数据。 事先不能预测这些事件“父母可读的孩子的输出”和“父母可以写的孩子的input”这些事件发生的顺序。
我build议阅读高级Linux编程 ,它有几个章节解释这些问题!
顺便说一句,我简单的事件循环有点不对劲:如果subprocess终止,一些数据仍然在其标准输出pipe道,它的阅读没有完成。 poll
结束后,您可以移动waitpid
testing
另外,不要指望一个单独的write
(从subprocess)进入一个pipe道会触发父进程中的一次read
。 换句话说,没有消息长度的概念。 但是,POSIX知道PIPE_MAX
….请参阅它的写文档。 传递给read
的缓冲区可能应该是PIPE_MAX
大小。
我再说一遍:你需要在你的事件循环中调用poll
,很可能会多次 调用poll
(因为你的循环会重复多次!),并且会报告可读或者可写的pipe道端点在一个不可预知的(不可重现的)订购! 您的程序的第一次运行可能会报告“ rpipe[0]
可读”,您从中read
324字节,您重复事件循环, poll
表示您“ wpipe[1]
可写”,您可以write
10个字节,事件循环, poll
告诉“ rpipe[0]
可读”,从中read
110个字节,重复事件循环, poll
再次告诉“ rpipe[0]
可读”,从中read
4096个字节等等。 ..在同一个环境中运行同一个程序会产生不同的事件,例如: poll
说“ wpipe[1]
可写”,你write
1000字节,重复循环, poll
表示“ rpipe[0]
可读等。
注意:你的问题不是在孩子(“客户”)程序中的缓冲,我们假设你不能改变。 所以重要的不是缓冲数据,而是真正的input和输出(这是父进程可以观察到的唯一事情;内部子缓冲与父进程无关),也就是您的子程序已经能够真的 读(2)和写(2) 。 如果通过pipe道(7) ,这些数据将在父进程中变为轮询(2) (并且在轮询之后 ,您的父进程可以在更新的POLL_OUT
字段中的POLL_IN
或POLL_OUT
之后read
或write
其中的一些)。 顺便说一句,如果你确实给孩子编了代码,别忘了在里面的适当的地方打电话给fflush
。
在你的代码中似乎有两个问题。 默认情况下,“stdout”是缓冲的 ,所以服务器应该明确地刷新它:
printf("I received %s\n", buffer); fflush(stdout);
并且POLLOUT
在尝试读取时不应注册POLLOUT
(但您可能需要注册POLLERR
):
pfds[0].fd = rpipe[0]; pfds[0].events = POLLIN | POLLERR;
通过这些修改,您可以获得预期的输出:
$ ./main 写数据... 正在读数据... 孩子说: 我收到你好
通常,您还应该检查poll()
的返回值,并在必要时重复呼叫(例如,在系统调用中断或超时的情况下)。