【问题标题】:EOF on Pipe is printing garbage管道上的 EOF 正在打印垃圾
【发布时间】:2013-10-23 17:45:22
【问题描述】:

编辑:因此,通过一些额外的调试,EOF 已成功写入管道(我知道这一点,因为我进行了测试以确保 write() 函数在 produceStdin 上返回 0。 但是从同一个管道读取时,它说我遇到了 EOF(好)但是 EOF 元素的值等于 255(而不是像它一样的 -1通常是)。有谁知道为什么会这样???

我正在尝试编写这个程序,但是当我遇到来自标准输入的 EOF 时,它并没有将 -1 写入管道。无论出于何种原因,当试图通过管道传递 EOF 时,垃圾都会被写入,因此所有后续进程都会陷入无限循环。

除了在printOut() 函数中打印数组之外的所有打印语句都是我尝试调试它(由于分叉而无法使用调试器)

另外:其中一些 cmets 被回收,所以如果您看到提到“缓冲区”,那是因为 这是以前使用缓冲区而不是管道编程的。

代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_CHARS 81 //80 chars + null-terminator
#define NUM_CHILDREN 3

void produceStdin(int writePipe);
void child1(int readPipe, int writePipe);
void child2(int readPipe, int writePipe);
void printOut(int readPipe);

int main(int argc, char const *argv[])
{
    int i,pipe1[2],pipe2[2],pipe3[2];
    pid_t childPid;

    if(pipe(pipe1)==-1||pipe(pipe2)==-1||pipe(pipe3)==-1)
    {
        fprintf(stderr, "Error in creating pipe");
    }
    //despite what it looks like only four children are being forked,
    // all to the same parent. The children get called to their respective 
    //functions where they get terminated before getting to fork themselves.
    for(i=0;i<NUM_CHILDREN;i++)
    {
        childPid=fork();
        switch (childPid) {
            case -1:
                perror("fork() failed. Aborting.");
                exit(EXIT_FAILURE);

            case 0:
                switch (i) {
                    case 0:
                        close(pipe1[0]); //close pipe1 read (since we're reading from stdin)

                        close(pipe2[0]); //close pipe2
                        close(pipe2[1]);

                        printf("right before calling stdin i=%d\n",i);
                        produceStdin(pipe1[1]); //write to pipe1
                        break;

                    case 1:
                        close(pipe1[1]); //close pipe1 write

                        close(pipe2[0]); //close pipe2 read

                        close(pipe3[0]); //close pipe3
                        close(pipe3[1]);
                        printf("right before calling child1 i=%d\n",i);
                        child1(pipe1[0], pipe2[1]); //read from pipe1, write to pipe2
                        break;

                    case 2:
                        close(pipe1[0]); //close pipe1
                        close(pipe1[1]);

                        close(pipe2[1]); //close pipe2 write

                        close(pipe3[0]); //close pipe3 read
                        printf("right before calling child2 i=%d\n",i);
                        child2(pipe2[0], pipe3[1]); //read from pipe2, write to pipe3
                        break;

                    default:
                        break;
                }

            default:
                if(i==2)
                {
                    close(pipe1[1]); //close pipe1
                    close(pipe1[0]);

                    close(pipe2[1]); //close pipe2
                    close(pipe2[0]);

                    close(pipe3[1]); //close pipe3 write

                    printOut(pipe3[0]); //read from pipe3 read
                }
                break;
        }
    }
    return 0;
}
void produceStdin(int writePipe)
{
    int c=0;
    while(c!=EOF)
    {
        c=fgetc(stdin);
        write(writePipe, &c, sizeof(char)); //writing EOF here is where the problem starts I believe
    }
    printf("Got EOF in ProdStdin\n");
    printf("EOF has a value of: %d",c);
    exit(0);
}
void child1(int readPipe, int writePipe)
{
    int c=0;
    while(c!=EOF)
    {
        read(readPipe,&c,sizeof(char));
//        printf("Child1 got a char from pipe1: %c\n",c);
        if(c=='\n')
        {
            c=' '; //test for newline
        }
        write(writePipe, &c, sizeof(char));
    }
    exit(0);
}
void child2(int readPipe, int writePipe)
{
    int c=0;
    int c2=0;
    while(c!=EOF && c2!=EOF)
    {
        read(readPipe, &c, sizeof(char));
//        printf("Child2 got a char from pipe2: %c\n",c);
        if(c=='*')
        {
            read(readPipe, &c2, sizeof(char)); //if c is a * remove another char
            if(c2=='*')
            {
                c='^'; //if c2 is a * then put a ^ on buffer3
                write(writePipe,&c,sizeof(char));
            }
            else
            {
                write(writePipe,&c,sizeof(char));
                write(writePipe,&c2,sizeof(char));
            }
        }
        else
        {
            write(writePipe,&c,sizeof(char));
        }
    }
    exit(0);
}
void printOut(int readPipe)
{
    int c=0,numChars=0;
    char output[MAX_CHARS];
    while (c!=EOF)
    {
        read(readPipe, &c, sizeof(char));
//        printf("PrintOut got a char from pipe3: %c\nnumChars= %d\n",c,numChars);
        if (numChars==MAX_CHARS-2)
        {
            printf("%s\n",output);
            memset(output, '\0', sizeof(char)*MAX_CHARS);
            numChars=0;
        }

        output[numChars]=c;
        numChars++;
    }
    printf("ABOUT TO EXIT PRINTOUT()\n");
    exit(0);
}

【问题讨论】:

    标签: c operating-system pipe eof


    【解决方案1】:

    替代传递通过管道读取的内容的 2 字节版本,以便接收端可以区分 char 和 EOF。

    int c = 0;
    while(c!=EOF) {
      c = fgetc(stdin);
      short sc = (short) c;
      // `sc` will _typically_ have the values -1 (EOF) and 0,1,2,... 255.
      write(writePipe, &sc, sizeof(sc));
    }
    
    int c=0;
    int c2=0;
    while(c != EOF && c2 != EOF) {
      short sc;          
      if (sizeof(sc) != read(readPipe, &sc, sizeof(sc))) handle_error();
      // `sc` will _typically_ have the values -1 (EOF) and 0,1,2,... 255.
      c = sc;
      ...
    

    原始建议答案。

    c 直到 读取所有字符后才会变为 EOF。
    使用:

    // while(c!=EOF) {
    //   c=fgetc(stdin);
    //   write(writePipe, &c, sizeof(char));
    // }
    while((c = fgetc(stdin)) != EOF) {
      write(writePipe, &c, sizeof(char));
    }
    

    应该评估read(readPipe, &amp;c, sizeof(char)); 的返回值,而不是寻找c 成为EOF。 EOF 不适合 char

    // int c=0;
    // int c2=0;
    // while(c!=EOF && c2!=EOF) {
    //    read(readPipe, &c, sizeof(char));
    
    char c=0;
    char c2=0;
    while(1 == read(readPipe, &c, sizeof(char))) {
    

    【讨论】:

    • 你能澄清一下吗?我的循环的编写方式是在退出之前将 EOF 写入管道。无论读取的字符是否为 EOF,我都想在退出之前将其写入管道,因此我的循环是 while(c!=EOF) 而不是 while((c=fgetc(stdin))!=EOF)
    • 换句话说,EOF 被成功读取然后写入管道,阅读我添加到帖子顶部的编辑可能很有用
    • 好的,所以我可以仅根据读取重新格式化循环。说,为了固执,我确实想将EOF写入管道然后读取它,可以这样做吗?另外,如果我没记错 EOF=-1 应该放在一个字节中,我会问这个问题,因为我专门设置了我的 c,c2 int 类型而不是 char 以适应 EOF 情况?
    • @sreya "EOF 在退出前写入管道"。这是不正确的,因为仅传递 1 个字节,您就失去了 EOF 和 char 之间的差异,例如 \377
    • @sreya,fgetc() 返回 256 个正数 + 1 个负数不同的值。要通过管道传递该信息,每次写入应使用 > 1 个字节。替代张贴写入 2。EOF 总是
    【解决方案2】:

    这点在写代码时有问题:

    int c = 0;
    
    while(c!=EOF)
    {
        c=fgetc(stdin);
        write(writePipe, &c, sizeof(char));
    }
    

    你应该使用:

    int c;
    
    while ((c = fgetc(stdin)) != EOF)
    {
        char c1 = c;
        write(writePipe, &c1, sizeof(char));
    }
    

    fgetc() 的输出是作为 int 或 EOF 返回的无符号字符值。实际上,您检测到 EOF,然后将一个字节写入管道,该字节确实不应该写入。使用c1 一般是必要的;您可能会逃脱在小端机器上编写的内容,但它在大端机器上无法正常工作。您可能也应该检查写入是否成功。

    等效阅读代码也有类似问题。你有:

    int c=0;
    while(c!=EOF)
    {
        read(readPipe,&c,sizeof(char));
        if(c=='\n')
        {
            c=' '; //test for newline
        }
        write(writePipe, &c, sizeof(char));
    }
    

    你需要:

    char c;
    while (read(readPipe, &c, sizeof(char)) == sizeof(char))
    {
        if (c == '\n')
            c = ' ';
        write(writePipe, &c, sizeof(char));
    }
    

    再一次,存在 little-endian 与 big-endian 的可移植性问题,以及输入操作的测试(总是,但总是检查输入操作;您通常可以不检查输出,但您必须始终检查输入)。

    请注意,通过 read()write() 的单字符 I/O 对于高性能应用程序来说是昂贵的。标准 I/O 单字符 I/O 是可以的,因为它缓冲输入或输出,并且不会在每次读取和每次写入时调用系统调用的开销。对于玩具应用程序,您不会注意到开销。

    这很可能是chux 在他的answer 中所说的,表达方式略有不同。

    【讨论】:

    • 是的,这是一个概念练习,不是表演练习,但感谢您的投入。如果您实际上不能传递 EOF,如何通过管道传递消息以让每个进程知道输入流已达到 EOF?只需传递一个自定义变量或其他什么?
    • 就像,我写完后才关闭管道,这似乎完成了工作
    • 是的;关闭管道让另一端知道没有更多数据——只要只有一个进程打开管道的写入端。因此,确保在一组中的所有进程中关闭未使用的管道描述符是很重要的。这通常是很多关闭。将“管道”视为“通过关闭所有不需要的管道来停止(文件描述符的)泄漏”。
    最近更新 更多