【问题标题】:Can't read more than one word from named pipe, using poll无法从命名管道中读取多个单词,使用 poll
【发布时间】:2015-01-13 04:28:10
【问题描述】:

基于answer,我正在模拟有两个作家和一个读者。

所以,我创建了两个管道,并为每个管道写入了一个实际字符串,一个字符串通知读者他已经完成了这个编写器。

但是,它只会读取第一个字符串,有时会读取第二个管道的结束字符串。

我错过了什么?

reader.c

int main() {
    int w_no = 2;
    int fd[w_no];
    char * myfifo[w_no];
    fill_names(myfifo, w_no);
    print_names(myfifo, w_no);
    struct pollfd fdtab[w_no];
    int done[w_no];

    /* create the FIFO (named pipe) */
    int i;
    for (i = 0; i < w_no; ++i) {
        //fd[i] = open(myfifo[i], O_RDONLY);
        while( (fd[i] = open(myfifo[i], O_RDONLY)) == -1); 

        fdtab[i].fd = fd[i];
        fdtab[i].events = POLLIN;
        fdtab[i].revents = 0;

        done[i] = 0;
    }

    char buffer[1024];
    ssize_t bytes;
    printf("Edw prin\n");
    while(not_all_done(done, w_no)) {
        int retpoll = poll(fdtab,  w_no, 300);
        if(retpoll != 0) {
             if (retpoll == -1) {
                 perror("poll");
                 break;
             }

             for(i = 0; i < w_no; ++i) {
                 if(fdtab[i].revents & POLLIN) {
                            printf("Edw %d %d %d %d\n", i, retpoll, fdtab[i].revents, POLLIN);
                     //read the written pipe
                     while((bytes = read(fdtab[i].fd, buffer, sizeof(buffer))) > 0)
                        printf("Read |%s| %d %d %d\n", buffer, retpoll, fdtab[i].revents, POLLIN);
                     if(!strcmp(buffer, "++"))
                        done[i] = 1;
                 }
             }
         } else if (retpoll == 0) {
                /* the poll has timed out, nothing can be read or written */
                printf("timeout from writer\n");
                break;
            }
    }

    for (i = 0; i < w_no; ++i) {
        close(fd[i]);
    }

    free_names(myfifo, w_no);
    return 0;
}

writer.c

int main() {
    int w_no = 2;
    int fd[w_no];
    char * myfifo[w_no];
    fill_names(myfifo, w_no);
    print_names(myfifo, w_no);

    /* create the FIFO (named pipe) */
    int i;
    int bytes;
    for (i = 0; i < w_no; ++i) {
      mkfifo(myfifo[i], 0666);
        fd[i] = open(myfifo[i], O_WRONLY);

        while( (bytes = write(fd[i], "Hi+", sizeof("Hi+"))) == 3);
        printf("wrote %d bytes, %d\n", bytes, sizeof("Hi+"));
        while( (bytes = write(fd[i], "++", sizeof("++"))) == 2);
        printf("wrote %d bytes, %d\n", bytes, sizeof("++"));
    }

    for (i = 0; i < w_no; ++i) {
        close(fd[i]);
        unlink(myfifo[i]);
    }

    free_names(myfifo, w_no);
  return 0;
}

示例输出:

/tmp/myfifo_0
/tmp/myfifo_0
/tmp/myfifo_1
/tmp/myfifo_1
wrote 4 bytes, 4
wrote 3 bytes, 3
wrote 4 bytes, 4
Edw prin
wrote 3 bytes, 3
Edw 0 2 17 1
Read |Hi+| 2 17 1
Edw 1 2 1 1
Read |Hi+| 2 1 1
^C

编辑

Hi+ 字符串到达​​时,bytes 的值为 7。

我试图发送的结束字符串是++,但它没有被读取。


EDIT_2

char* concat(char *s1, char *s2) {
  char *result = malloc(strlen(s1) + strlen(s2) + 1);  //+1 for the null-terminator
  //in real code you would check for errors in malloc here
  strcpy(result, s1);
  strcat(result, s2);
  return result;
}

void fill_names(char* f[], int n) {
  int i = 0;
  char * buf = "/tmp/myfifo_";
  char str[15];
  for (; i < n; ++i) {
    sprintf(str, "%d", i);
    f[i] = concat(buf, str);
  }
}

想法

也许作者在从管道中读取数据之前关闭并取消链接?如果是这样,我应该怎么做才能防止这种情况发生?

如果在此之前放一个sleep(10),它不会改变行为,它只会读取前两个字符串,但需要更多时间然后它会挂断(因为它等待结束字符串)。


EDIT_3

我还有一个 main.c,它执行读取器和写入器。

【问题讨论】:

  • 您能说明fill_names() 的定义吗?我的印象是您使用静态来构建名称。
  • 当然是@Christophe,感谢您抽出宝贵时间。
  • concat 是否使用静态缓冲区?还是返回一个 strdup() ?我之所以问,是因为您在日志开头显示了每个管道的名称的两倍,就好像您会使用相同的管道一样。
  • while( (bytes = write(fd[i], "Hi+", sizeof("Hi+"))) == 3); 表示只要只写3个字节就会循环再写一次,不是吗?
  • 天哪,我怎么错过了?好的@Christophe,在你的回答中你可以解释我需要改变什么。

标签: c ipc named-pipes system-calls polling


【解决方案1】:

你的字符串写有问题:

    while( (bytes = write(fd[i], "Hi+", sizeof("Hi+"))) == 3);
    printf("wrote %d bytes, %d\n", bytes, sizeof("Hi+"));
    while( (bytes = write(fd[i], "++", sizeof("++"))) == 2);
    printf("wrote %d bytes, %d\n", bytes, sizeof("++"));

这里发送 7 个字节:H i + \0 + + \0,因为字符串的 sizeof() 包含空终止符。

顺便说一句,只要可以写入 3 个字节,while((bytes=write(...))==3) 就会循环。这不会在这里发生,因为您的写作也使用了空终止符。但最好去掉封闭的while

由于管道是一个流,因此没有任何保证您将在两个不同的读取中接收到字节。事实上,你所有的解释和日志都表明你一次收到了所有 7 个字节。

但是,您使用printf("Read |%s| %d %d %d\n"...) 打印内容:打印包含“\0”的字符串的结果是未定义的。在您的情况下,打印的字符串被截断。所以只打印“Hi+”,但“\ 0++" 仍然隐藏在缓冲区中。

顺便说一句,while((bytes = read(...)) &gt; 0) 可以循环打印多次。这本身不是问题。只是如果写入器及时发送数据,连续读取可能会暂时锁定其他管道的读取。一般来说,在轮询程序中,人们更喜欢从每个准备好的管道中读取一点点。

您对结束字符串的检查

    if(!strcmp(buffer, "++"))
          done[i] = 1;

在大多数情况下可能不会成功。您不确定一侧的写入会导致另一侧的读取。所以你的“++”字符串不一定在缓冲区的开头。它可能在缓冲区中的任何位置,因此您必须搜索它。它甚至可以在两个连续读取之间拆分。

顺便说一句,read() 可能只找到不带终止 null 的部分数据(例如:“i+”)。如果您假设其中有一个有效的字符串,然后尝试打印您的缓冲区,那么您将面临缓冲区溢出的风险。

推荐:

如果您的命名管道用于处理文本数据,我建议在您要发送的每组数据的末尾添加一个'\n',并将字符串写入管道而无需终止 null:

bytes = write(fd[i], "Hi+\n", sizeof("Hi+\n")-1);

然后,当您阅读时,您可以像管理字符串一样管理缓冲区:始终添加尾随 0:

bytes = read(fdtab[i].fd, buffer, sizeof(buffer)-1);  // leave a byte for terminator
if (bytes>0) {
    buffer[bytes]=0; // end of string.  
    // process the string in the buffer
}
else if (bytes==0) {
    done[i]=1; 
} 

最后为了识别你的结束命令,假设你已经将它发送为“++\n”,有三种可能性:

if (strncmp(buffer,"++\n",3)==0  /* it's at the beginning of the buffer */ 
      || strstr(buffer, "\n++\n") )    /* it's in the middle but not a subpart and preceded by a packet separator */
       done[i]=1;   

但您还必须检查两次读取之间的拆分。这更微妙,但我相信你会找到方法 ;-)

【讨论】:

    【解决方案2】:

    尝试同时打印您在阅读器程序中读取的字节数。我的猜测是您读取的字节数比您想象的要多,但是在打印字符串时,您只会得到第一个终止零之前的字节。

    您是否打算在命名管道上发送终止零?也许发送换行符之类的其他内容会更好?

    【讨论】:

    • 我发送++ 作为结束字符串。检查你说的和我在Read |Hi+|时得到的,bytes的值是7。这是什么意思,我该如何解决?
    • 这个答案看起来更像是评论而不是真正的答案。这里有什么解决方案?
    • 是的,应该是评论。但我猜他没有足够的声誉。如果他找到解决方案,我会告诉他编辑答案,使其成为真正的答案。否则,它必须被删除。 ://
    • 因此,即使您确实感觉良好,但这应该是评论,而不是答案,因此您可能想删除它。不过谢谢! :)
    • 解决方案是,您实际上从管道中读取的内容比您想象的要多。你想知道为什么你只读一个词,我解释说你读了多个词,但只用 printf 呈现一个词。是的,这只是一个猜测,没有完整的代码我无法在调试器中编译和验证。如果您出于某种原因想要删除我的答案,请随意这样做,我的所有建议,包括用换行符重新命名零现在也包含在 Christophe 的答案中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-09
    • 1970-01-01
    • 2017-12-12
    相关资源
    最近更新 更多