2013-12-14 32 views
0

我试图使用poll函数C中的流程如下实行通信系统的客户端 - 服务器类型:用缓冲使用轮询功能流

  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; 
} 
} 

服务器代码很简单:

int main() { 
    char *buffer = (char *) malloc(1000); 

    while (scanf("%s", buffer) != EOF) 
    { 
     printf("I received %s\n", buffer); 
    } 
    return 0; 
} 

如何防止定时poll电话,因为缓冲?

编辑:

我想程序甚至工作时exec ED的二进制是外部的,即I没有控制权的代码 - 就像一个unix命令,例如,catls

+0

为什么子进程关闭所有管道? 'close(wpipe [0]);关闭(RPIPE [0]); close(wpipe [1]);关闭(rpipe [1]);'???? –

+0

@GiuseppePes:fds被复制为标准输入/标准输出,因此关闭它们似乎没问题。 –

+0

@MartinR谢谢!我错过了dub2电话! :( –

回答

1

您需要,因为我在related answer回答您以前的问题,实施event loop;因为它顾名思义,它是循环,所以你应该在父进程的代码:

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),就可以读取,并且只要输出管道被轮询为可写(即不是可写)充分)。您无法事先预测这些事件“父母可读的孩子的输出”以及“父母可以写入的孩子的输入”这些事件发生的顺序。

我推荐阅读Advanced Linux Programming,它有几个章节解释这些问题!

顺便说一下,我简单化的事件循环有点不对:如果子进程终止并且一些数据保留在其标准输出管道中,则其读取未完成。您可以在poll

后也动waitpid测试,不要指望一个write(从子进程)到管道会在父进程触发一个单一read。换句话说,没有消息长度的概念。然而,POSIX知道约PIPE_MAX ....参见其write文档。可能您的缓冲区传递到readwrite应该是PIPE_MAX大小。

我再说一遍:你需要通话poll您事件中循环,很可能poll将被称为几次(因为你的循环将被重复很多次!),并将报告读写管以不可预知(不可重现)的顺序结束!您的程序的第一次运行可能会报告“rpipe[0]可读”,您从其中读取324个字节,您重复事件循环,poll说您“wpipe[1]可写”,您可以将其中的write 10个字节复制到它,您重复事件循环,poll告诉那“rpipe[0]可读”,你read从它110个字节,你重复事件循环,poll再次告诉“rpipe[0]可读”,你read 4096字节从它等等等等......第二次运行同一程序在同一个环境会给出不同的事件,如:poll表示“wpipe[1]可写”,你write 1000字节到它,你重复循环,poll说“rpipe[0]可读,等等......

注意:您的问题不是孩子(“客户”)程序中的缓冲,我们假设您无法更改。所以重要的不是它的缓冲数据,而是输入和输出(这是父进程可以观察的唯一东西;内部子缓冲与父代无关),即您的子程序已经存在的数据能够真的read(2)write(2)。如果要通一个pipe(7),这些数据将成为poll(2) -able父进程(和你的父进程可以readwrite一些吧poll后的更新reventsPOLL_INPOLL_OUT后)。顺便说一句,如果你确实给孩子编码,不要忘记在里面的适当位置拨打fflush

+0

请考虑以下事件序列: 1.父级写入wpipe [1] 2.子级从stdin(我已将其复制到wpipe [0])中读取,将其响应输出到标准输出 3.由于stdout在子级进程被缓存,响应没有立即到达父节点 4.父节点不能发送它的下一条消息,因为正如我在问题中所述,响应是在处理最后收到的消息之后确定的。家长至今尚未收到来自子进程的任何消息。因此,父程序块。 如何用简单的事件循环来处理? – prvnsmpth

+0

您不应该关心子进程中缓冲的内容。你不能改变这一点。只关心孩子真正写的和读的内容,即关于什么是父母分别可读和可写的内容,因此事件*循环*应该真正重复,就像每个循环一样...... –

+0

重要的是你需要一个*循环*,并且你需要在这样一个循环内多次调用'poll' .... –

2

在你的代码中似乎有两个问题。 “标准输出”是由缓冲默认, 所以服务器应该明确地刷新它:

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

,并试图读取 当主程序不应注册为POLLOUT(但你可能要注册POLLERR):

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

有了这些修改,你获得预期的输出:

 
$ ./main 
Writing data... 
Reading data... 
child says: 
I received hello 

一般情况下,您还应该检查返回值poll(),如果必要(例如 ),请重复呼叫。在中断系统调用或超时的情况下)。

+0

上述解决方案将如果我可以修改服务器代码,但是如果我不能这样做会怎么样?如果我想运行一个像unix命令'cat'的外部程序? – prvnsmpth

+0

@prvnsmpth:从你的问题中我得到了你拥有服务器的印象代码,因此可以修改它,即使使用“cat”,如果你设置了'pfds [0] .events = POLLIN | POLLERR'就好像我建议的那样,但是如果外部程序缓冲它输出(因此不写如果外部进程读取了输入,但是没有写入响应,那么就没有*你可以做的事情。 –

+0

poll应该总是在一个*循环内重复,因为一个管道可以多次读取! (不仅仅是因为系统调用或超时中断) –