如何等待数据写入管道的另一端

8

我正在开发一款C语言应用程序。 父子进程通过管道进行通信。 在写入管道之前,父进程会执行其他语句。在示例代码中,我使用了sleep(10)来制造延迟。 在子进程中,它应该从管道中读取数据。 但是在子进程的读端口上没有读取到数据。

int main()
{
    int pid;
    FILE *fp;
    fp = fopen("test.txt","w");
    char *buff;
    int fd[2];
    int count = 0 ;
    pipe(fd);
    pid = fork();
    if(pid == 0)
    {
        close(fd[1]);
        ioctl(fd[0], FIONREAD, &count);
        fprintf(fp,"Value of count: %d ",count);
        buff = malloc(count);
        fprintf(fp,"\n TIME before read: %s",__TIME__);
        read(fd[0], buff, count);
        fprintf(fp,"\nbuffer: %s\n TIME after read %s", buff, __TIME__);
    }
    else{
        close(fd[0]);
        sleep(10);    //delay caused by application specific code replaced with sleep
        write(fd[1],"THIS is it",10);
    }
    fclose(fp);
    return 0;
}

如何使子进程等待直到数据被写入另一端?
3个回答

6

您的管道以阻塞模式打开,并且您没有采取任何措施来更改它,这很可能是您想要的。

但是,由于您做的第一件事情是请求等待在管道中的数据大小,然后盲目地跳入读取那么多字节(在代码执行该操作时,这些字节很可能为零,因为父进程还没有写入任何内容),所以您不会阻止,而是什么都不做就离开了。

有许多方法可以做到这一点,其中包括选择循环。如果您宁愿在读取时阻塞直到有数据可用,则可以在单个字节上这样做,并在填充剩余数据之后继续。

这绝不是如何做到这一点的示例正确方法,但它是一个简短的示例,展示了如何等待单个字节,请求管道的读取大小以获取其余数据,读取它,并继续执行,直到管道没有数据,父进程关闭了它们的端口:

希望您觉得这有帮助。

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main()
{
    int pid = 0;

    // create pipe pair
    int fd[2];
    pipe(fd);

    pid = fork();
    if (pid == 0)
    {
        // child side
        char *buff = NULL;
        char byte = 0;
        int count = 0;

        // close write side. don't need it.
        close(fd[1]);

        // read at least one byte from the pipe.
        while (read(fd[0], &byte, 1) == 1)
        {
            if (ioctl(fd[0], FIONREAD, &count) != -1)
            {
                fprintf(stdout,"Child: count = %d\n",count);

                // allocate space for the byte we just read + the rest
                //  of whatever is on the pipe.
                buff = malloc(count+1);
                buff[0] = byte;
                if (read(fd[0], buff+1, count) == count)
                    fprintf(stdout,"Child: received \"%s\"\n", buff);
                free(buff);
            }
            else
            {   // could not read in-size
                perror("Failed to read input size.");
            }
        }

        // close our side
        close(fd[0]);
        fprintf(stdout,"Child: Shutting down.\n");
    }
    else
    {   // close read size. don't need it.
        const char msg1[] = "Message From Parent";
        const char msg2[] = "Another Message From Parent";
        close(fd[0]);
        sleep(5); // simulate process wait
        fprintf(stdout, "Parent: sending \"%s\"\n", msg1);
        write(fd[1], msg1, sizeof(msg1));
        sleep(5); // simulate process wait
        fprintf(stdout, "Parent: sending \"%s\"\n", msg2);
        write(fd[1], msg2, sizeof(msg2));
        close(fd[1]);
        fprintf(stdout,"Parent: Shutting down.\n");
    }
    return 0;
}

输出

Parent: sending "Message From Parent"
Child: count = 19
Child: received "Message From Parent"
Parent: sending "Another Message From Parent"
Parent: Shutting down.
Child: count = 27
Child: received "Another Message From Parent"
Child: Shutting down.

1

我认为在之后

ioctl(fd[0], FIONREAD, &count);  
the count is 0.  

read(fd[0], buff, count) will get no data.  

try   
read(fd[0], buff, 10)

我使用ioctl来确定管道末尾数据的大小,因为父进程发送的数据长度是可变的。 - ajay_t
@Ajay 正确,但是在你触发它的时候,管道中还没有任何数据。因此你最终请求了 字节,这并不会阻塞。 - WhozCraig
@WhozCraig 有没有办法让ioctl(xxxx)函数等待,直到在管道末端有可用数据,就像read()等待数据一样。 - ajay_t
@Ajay 我不知道他是否使用了 ioctl(),但这并不重要。如果你担心缓冲区的大小,你可以始终阻塞在单字节读取上,并在成功后吸收管道中剩余的任何内容。或者只需创建一个大型庞大的缓冲区并将其读入其中。选择最终取决于您。 - WhozCraig

1
问题在于获取写入管道的字节数。如果在fork()之后立即获取,如果读取过程先执行,则不会包含任何数据(计数为零)。如果写入进程先执行,则会包含一些数据。
如何使子进程等待直到另一端的数据被写入?
由于您以阻塞模式打开了管道,因此应尽可能多地读取数据,而不是尝试获取已写入数据的大小。
这是您修改后的示例,它等待完整的消息:
#include <stdio.h>
#include <sys/ioctl.h>
int main()
{
int pid;
FILE *fp;
fp = fopen("test.txt","w");
char *buff = malloc(1024);
int fd[2];
int count = 0 ;
pipe(fd);
pid = fork();
        if(pid == 0)
        {
                close(fd[1]);

                int i = 0;

                while ( i < 10 )
                {
                    fprintf(fp,"\n TIME before read: %s \n",__TIME__);
                    read(fd[0], buff+i, 1);
                    ++ i;
                }

                fprintf(fp,"Full message received!\nbuffer: %s\n TIME after read %s\n", buff, __TIME__);
        }
        else{
                close(fd[0]);
                sleep(10);    //delay caused by application specific code replaced with sleep
                write(fd[1],"THIS is it",10);
        }
fclose(fp);
return 0;
}

没错,但是父进程每次写入的数据大小都不同,这就是为什么我使用ioctl来获取管道中数据的大小。我能阻塞ioctl直到管道接收到数据吗? - ajay_t
@Ajay 不,没有办法阻止 ioctl() - BЈовић
1
@Ajay 说实话,如果大小很重要,我强烈建议不要使用ioctl()获取此信息。从您调用它的时间到您进行读取操作的时间,由于管道另一端写入更多数据,大小可能已经无效了。如果您想读取父进程将要发送的字节数,则将其作为您协议的一部分(例如,前四个字节是32位网络字节序消息长度)。 - WhozCraig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接