【问题标题】:Strange letter when passing character through a pipe通过管道传递字符时出现奇怪的字母
【发布时间】:2023-03-14 06:18:01
【问题描述】:

我有一个内容为abcefghz 的输入文件。我想使用管道,以便子进程 p1 每次向其父进程发送 1 个字母,父进程将使用 ASCII 码+1 转换此字符串(abcefghz 将变为cdfghi{)。这个新字符串将通过另一个管道发送到另一个子进程,该子进程将在输出文件上打印结果。

这是代码:

int main (int argc, char **argv)
{  
    pid_t pid1, pid2;
    int inputFile, outputFile;
    char stringaDalFile[256];
    char successivo;
    char stringaRisultato[256];
    int fd1[2], fd2[2]; // Pipe



    inputFile = open(argv[1], O_RDONLY);
    outputFile = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);

    pipe(fd1);
    pipe(fd2);

    pid1 = fork();
    if (pid1 == 0) { 

        while ( (nread=read(inputFile, stringaDalFile, 1)) > 0) {
            close(fd1[0]);
            write(fd1[1], stringaDalFile, 1);
        }

        close(inputFile);
    }
    else { 
        close(fd1[1]);
        while ( read(fd1[0], stringaDalFile, 1) > 0 ) {

            successivo = converti(stringaDalFile[0]);

            write(fd2[1], &successivo, 1);
        }
    }

    pid2 = fork();
    if (pid2 == 0) { 

        close(fd2[1]);

        if (read(fd2[0], stringaRisultato, 1) == -1) {
            perror("Errore");
            exit(1);
        } 
        else {
            while ( read(fd2[0], stringaRisultato, 1) > 0 ) {
                write(STDOUT_FILENO, stringaRisultato, strlen(stringaRisultato)); //dbg
write(outputFile, stringaRisultato, strlen(stringaRisultato));
            }
        }

        close(outputFile);

        exit(0);
    }

    return 0;
}

char converti (char carattere) 
{
    return carattere+1;
}

不幸的是,这似乎不能 100% 工作,字符串被转换但程序进入了一个似乎是无限循环的状态:

如果我CTRL-Cgedit file2.txt,这是它的内容:

.

我该如何解决这个问题?

【问题讨论】:

  • 您永远不会将stringaDalFile 初始化为任何值,因此它永远不是正确的'\0' 终止字符串。
  • 我该如何解决?
  • 您似乎只使用了stringaDalFile[0],因此它不必以 NUL 结尾。但它也不必是大小为 256 的数组,1 应该就足够了。
  • 但是令人担忧的问题是:奇怪的是,您甚至在凝视 p2 之前就将转换后的数据写入管道。对于大量不起作用的数据。而且您不会关闭父级管道的写入端,因此 p2 永远不会获得 EOF 而是无限等待更多数据
  • 顺便说一句:你使用strlen(stringaRisultato)。如果您没有终止缓冲区,那 错误的。但就像在其他循环中一样,如果您一次只读取一个字节,则可以将 strlen() 替换为 1。

标签: c file io pipe system-calls


【解决方案1】:

你的代码有几个问题:

  1. 真正的问题,在这里:

    write(outputFile, stringaRisultato, strlen(stringaRisultato));
    

    stringaRisultato 开头只有一个有效字符且后面没有 NUL 终止符时,您正在使用 strlen(stringaRisultato)。只需改用1

  2. 您一次只能读取一个字符,不需要 256 个字符的字符串。将stringaDalFilestringaRisultato 更改为两个单独的char 变量:carattereDalFilecarattereRisultante

  3. 在第一个孩子(if (pid1 == 0) 内)中,您正在循环中执行close(fd1[0])。你应该把它移出while

  4. 再次在第一个孩子中,你没有做exit(0)。这就是导致您的程序继续运行的原因。

  5. 第二个孩子里面的这一行:

    if (read(fd2[0], stringaRisultato, 1) == -1) {
    

    仅在您想跳过第一个字符时才有用。这是故意的吗?如果没有,请删除 if 并仅使用 while (read(...) > 0)

  6. 在启动第二个孩子(将从它读取)之前,您正在将所有内容写入管道fd2[1]。如果输入文件太大(一些 KB),这将导致管道内部缓冲区被填满,并会在它尝试执行的下一个 write() 上阻塞您的程序,使其永远不会结束。要解决此问题,您应该一起启动两个子进程。这需要更改代码的结构和逻辑,但这是可行的。

    因为我认为这不是这里真正的问题,而且这个程序很可能是为了教育目的而编写的,所以我将把它留给你并解决上述其余问题。


工作代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

char converti (char carattere);

int main (int argc, char **argv)
{
    pid_t pid1, pid2;
    int inputFile, outputFile;
    char carattereDalFile, carattereRisultante, successivo; // renamed
    int fd1[2], fd2[2];

    inputFile = open(argv[1], O_RDONLY);
    outputFile = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);

    pipe(fd1);
    pipe(fd2);

    pid1 = fork();
    if (pid1 == 0) {
        close(fd1[0]); // <==  moved out of the while loop

        while (read(inputFile, &carattereDalFile, 1) > 0) {
            write(fd1[1], &carattereDalFile, 1);
        }

        close(inputFile);
        exit(0); // <== added
    } else {
        close(fd1[1]);

        while (read(fd1[0], &carattereDalFile, 1) > 0) {
            successivo = converti(carattereDalFile);
            write(fd2[1], &successivo, 1);
        }
    }

    pid2 = fork();
    if (pid2 == 0) {
        close(fd2[1]);

        if (read(fd2[0], &carattereRisultante, 1) == -1) {
            perror("Errore");
            exit(1);
        } else {
            while (read(fd2[0], &carattereRisultante, 1) > 0) {
                write(outputFile, &carattereRisultante, 1);
            }
        }

        close(outputFile);
        exit(0);
    }

    return 0;
}

char converti (char carattere)
{
    return carattere+1;
}

【讨论】:

  • 仍然会有管道缓冲区溢出,如果输入文件变得太大,可能会导致父级阻塞write(),因为在第二个子级启动之前所有转换的数据都已写入管道
  • @IngoLeonhardt 没有“管道缓冲区溢出”之类的东西。管道缓冲区不能溢出。当它变满时,跟随write()s 将阻塞,直到另一边有read()。这当然是 OP 代码中的一个问题,但解决方案相当复杂。 OP 的程序很可能只是为了获得一些管道和fork() 的经验而编写的。只要输入文件的大小小于几 KB(我认为这是真的),一切正常。
  • 其实解决方法很简单。首先启动两个子进程,然后启动父进程中的读/写循环。当然,“管道缓冲区溢出”不是正确的措辞,但无论如何你已经明白了我的意思。
  • Grazie Marco, sei stato gentilissimo。非常感谢
【解决方案2】:

read 返回读入的字节数,因此您应该使用它将 NUL 终止字符添加到这样填充的字符串中

while ( (bytesread = read(inputFile, stringaDalFile, 1)) > 0) {
   stringaDalFile[bytesread] = '\0';
....

【讨论】:

  • 如果您一次只读写一个字节,则不需要。不涉及标准输入输出
  • @IngoLeonhardt 他们将字符串传递给strlen,所以它需要被 NUL 终止 - 不确定 stdio 与任何事情有什么关系
  • stlen() 永远不会为stringaDalFile 调用,只为stringaRisultato 调用(当然,这是一个错误。任何是的,'stdio' 是错误的。我的意思是'srinng handling'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-23
  • 2020-10-19
相关资源
最近更新 更多