如何在Linux中防止从管道读端读取垃圾数据

4
我使用管道和fork编写了一小段代码。子进程调用子函数写入管道,父进程调用父函数从管道中读取。
问题出现在fork()后的第一次程序调用时进入了父函数。这里关闭了写端。现在的问题是,读取调用将一些垃圾存入buf中,nread返回一个大于0的值。如何防止这种情况发生?
使用Linux 2.6.32-30-generic和gcc 4.4.3。以下是代码:
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#define MSGSIZE 16

void parent(int* p);
void child(int* p);

char* msg1 = "hello";
char* msg2 = "bye";

int main()
{
    int pfd[2];
    if(pipe(pfd) == -1)
    {
        printf("Unable to create pipe\n");
        exit(1);
    }
    fcntl(pfd[0],F_SETFL,O_NDELAY);

    if(fork() == 0)
        child(pfd);
    else
        parent(pfd);

    return 0;
}

void parent(int p[2])
{
    int nread;
    char buf[MSGSIZE];
    buf[0] = '\0';

    close(p[1]);
    for(;;)
    {
        nread = read(p[0] , buf , MSGSIZE);
        if(nread == 0)
        {
            printf("pipe Empty/n");
            sleep(1);
        }
        else
        {
            if(strcmp(buf,msg2) == 0)
            {
                printf("End of conversation\n");
                exit(0);
            }
            else
                printf("MSG=%s\n" , buf);
        }
    }
}

void child(int p[2])
{
    int count;
    close(p[0]);
    for(count = 0 ; count < 3 ; count++)
    {
        write(p[1],msg1 , MSGSIZE);
        sleep(3);
    }
    write(p[1],msg2,MSGSIZE);
    exit(0);
}

1
你正在向管道写入16个字节,但字符串只有5个字节(包括终止符'\0'在内的6个字节)。当你收到垃圾数据时,nread是什么?你接收到的字符串是什么? - Some programmer dude
4个回答

4

一个问题是:

char buf[MSGSIZE];
buf[0] = '\0';

这只会将buf中的第一个字符设置为null终止符:其余的字符在buf中未初始化。 read() 正试图读取 16 个字节,这意味着buf中的字符将不被null终止,而printf("%s", buf)要求buf是null终止的。即使buf已经正确初始化,由于它的大小是16,而read()也读取了16,因此没有空间留给null终止符。

可能的解决方法如下:

char buf[MSGSIZE + 1] = ""; /* +1 added to store the null terminator and
                               all characters set to 0 (null terminator). */

另一个问题是 write() 函数(由 Joachim Pileborg 评论):

write(p[1],msg1 , MSGSIZE);
write(p[1],msg2 , MSGSIZE);

msg1msg2的长度不是16字节。请更改为:

write(p[1],msg1 , strlen(msg1));
write(p[1],msg2 , strlen(msg2));

此外,read() 在失败时返回-1,因此以下代码不足以满足要求:
nread = read(p[0] , buf , MSGSIZE);
if(nread == 0)
{
    ...
}

同时还需检查是否存在-1

else if(nread == -1)
{
    fprintf(stderr, "read() failed: %s\n", strerror(errno));
}
else
{
    ...
}

编辑:

请参考nos的答案,关于阻塞和非阻塞配置问题。


请注意,使用这种方法,read() 可能无法返回完整的字符串,这可能或可能不重要,具体取决于如何处理读取的数据。 - nos
@nos,我错过了非阻塞配置。鉴于存在strcmp()函数,我认为需要完整的字符串。 - hmjd

3

你真正的问题在于这一行代码:

fcntl(pfd[0],F_SETFL,O_NDELAY);

这将读端管道设置为非阻塞状态。因此,每次read()调用将返回缓冲区中的所有数据,如果此时没有可读取的数据,则返回-1并将errno设置为EWOULDBLOCK。

但是,您的代码没有处理这种情况,它只检查if(nread == 0),并在未读取任何内容的情况下打印出缓冲区。因此,请删除该行。

如果您不想发送固定大小的消息,或者想保持读端非阻塞状态,那么情况会变得更加棘手,因为您必须考虑至少以下几种情况:

  • read()返回-1,并将errno设置为EWOULDBLOCK(再次尝试read())。
  • read()读取您的“消息”的前4个字节,下一次读取返回消息的剩余部分。
  • read()读取第一条消息以及随后消息的一半。

也就是说,除非您只需要进一步流传输管道内容,否则您需要对消息进行某种形式的帧/分隔符处理。


0

Read不会将输入以nul结尾。

如果要打印一个没有以nul结尾的字符缓冲区,请按照以下方式操作:

printf("MSQ=%.*s\n", nread, buf);

如果您想要将读缓冲区变为零终止形式,需要进行两个更改。

1. 增加缓冲区的大小至MSGSIZE+1:

char buf[MSGSIZE + 1];

2. 每次读取后在缓冲区末尾添加空终止符。

buf[nread > 0 ? nread : 0] = 0;  // read returns -1 on error

0

另外

msg1msg2是小于MSGSIZE的字符串字面量。

关于垃圾的事情,有一个原则叫做GIGO:输入垃圾,输出垃圾。

不想让垃圾进入管道?在你的厨房水槽上使用排水阻拦器。


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