C管道写入/读取双打序列失败

Posted

技术标签:

【中文标题】C管道写入/读取双打序列失败【英文标题】:C pipe to write / read sequence of doubles fails 【发布时间】:2020-05-21 15:14:44 【问题描述】:

对于这个 C 项目,我正在尝试使用管道让父进程与子进程进行通信。子进程应该从 txt 文件(包含实数)中读取行(每秒一行)并使用管道来提供父进程,父进程又应该从管道中读取并将数字写入日志文件。然而父进程只读取 0.000000 的序列。这是我的代码:

#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <wait.h>
#define MAX_I 5
#define delay 1
void read_input(FILE *fp);

FILE *file_log;
int status;
pid_t pid;
int pipeFD[2];

int main(int argc, char **argv) 
//initialize
    FILE *fp;

    if (argc != 2)
    if ((fp = fopen("input.txt", "r")) == NULL) 
        printf("Error! opening file");
        exit(1);
    
    else
        fp = fopen(argv[1], "r");
        

    if (pipe(pipeFD) == -1)    /* creates a pipe */
    fprintf(stderr, "\nERROR: pipe() failed\n");
    exit(1);

    file_log=fopen("file.log","w+"); /*open the log file*/

    pid=fork();
    if(pid==0) //child process
    printf ("%d starts \n", getpid());
    close(pipeFD[0]);//close read end for child
    read_input(fp);
    return 0;
    exit(status);
    else//parent 
    double speed_read;
    close(pipeFD[1]);//close write end for parent
    while(1)
        if (read(pipeFD[0], &speed_read, sizeof(speed_read)) >0)
        if (speed_read<0)
        break;
        fprintf(file_log, "%f \n", speed_read); 
        printf("process %d received %f from child \n",getpid(),speed_read);
        else
            printf("Nothing there to read \n");  
        
    printf("parent ended \n");
    wait(&status);
    fclose(fp);
    fclose(file_log);
    
    return 0;


void read_input(FILE *fp)
    char *line = NULL;
    double speed;
    int i=0; size_t len = 0; double exit_sign =-10.0;
    while(getline(&line, &len, fp) != -1)  
        speed=atof(line);
        i++;
        if(i>MAX_I)//reads up to MAX_I rows of input
                        printf("I'll send the exit sign to parent now\n");
                        write(pipeFD[1], &exit_sign, sizeof(double));
                        free(line);
                        break;      
                    
        if(write(pipeFD[1], &speed, sizeof(double)>0))
            printf("%d at %d wrote that speed is %f\n",getpid(), i,speed);
        elseprintf("Write on pipe failed\n");
        sleep(delay);
        
    free(line);

这是打印的内容:


15032 开始

15032 at 1 写道速度为 0.345670

进程 15031 从子进程收到 0.000000

15032 at 2 写道速度是 12.678890

进程 15031 从子进程收到 0.000000

15032 at 3 写道速度是 34.789870

进程 15031 从子进程收到 0.000000

15032 at 4 写道速度是 0.000000

进程 15031 从子进程收到 0.000000

15032 at 5 写道速度是 12.009288

进程 15031 从子进程收到 0.000000

我现在将退出标志发送给家长

父级结束


同样,日志文件包含以下内容:

0.000000

0.000000

0.000000

0.000000

0.000000

【问题讨论】:

【参考方案1】:

read_input 行:

if(write(pipeFD[1], &speed, sizeof(double)>0))

必须

if(write(pipeFD[1], &speed, sizeof(double))>0)

在您的情况下,您不写 sizeof(double) 字节,而只写 1

注意 read_input 两次 free(line); 的行为未定义,您必须在 if(i&gt;MAX_I) 时删除一次

修正后编译执行:

pi@raspberrypi:/tmp $ gcc -g -Wall p.c
pi@raspberrypi:/tmp $ cat input.txt 
1.2
2.3
3.45
7.8
9.12
12.345
pi@raspberrypi:/tmp $ ./a.out
15121 starts 
15121 at 1 wrote that speed is 1.200000
process 15120 received 1.200000 from child 
15121 at 2 wrote that speed is 2.300000
process 15120 received 2.300000 from child 
15121 at 3 wrote that speed is 3.450000
process 15120 received 3.450000 from child 
15121 at 4 wrote that speed is 7.800000
process 15120 received 7.800000 from child 
15121 at 5 wrote that speed is 9.120000
process 15120 received 9.120000 from child 
I'll send the exit sign to parent now
parent ended 
pi@raspberrypi:/tmp $ cat file.log
1.200000 
2.300000 
3.450000 
7.800000 
9.120000 
pi@raspberrypi:/tmp $ 

除此之外,当您检测到 EOF 并执行 printf("Nothing there to read \n"); 以同时执行 break; 以完成 while 似乎更好。请注意,您还可以在 read_input 中到达输入文件的末尾时发送 exit_sign。如果您不这样做,如果输入文件的行数少于 MAX_I 行,则父级永远不会结束写入 Nothing there to read


管道但reading or writing pipe data is atomic if the size of data written is not greater than PIPE_BUFPIPE_BUF is at least 512 bytes, and 4096 under Linux

因此,在 main 中,您尽可能快地读取并且 read_input 函数处于休眠状态,而当您读取和写入相同的少量字节时,您有没有风险。否则,通常当您在流中读取时,您需要循环直到获得预期的字节数,例如通过调用以下函数来替换 read 的使用:

int read_block(int f, char * p, size_t sz)

  ssize_t n;

  while ((n = read(f, p, sz)) > 0) 
    if ((sz -= (size_t) n) == 0)
      return 1;
    p += n;
  

  return -1;

【讨论】:

另外,代码应该为部分读/写的可能性做好准备,它处理至少 1 个字节但不是完整的sizeof(double) @NateEldredge 是的,管道就是流。我没有,因为我担心这对于 OP 来说太复杂了,但最后你帮助我改变主意,我会编辑我的答案 @布鲁诺。这真的很有帮助。我的代码确实有很多错误。我会利用你的其他 cmets 来改进它。 写入小于管道缓冲区大小的管道是原子的,sizeof(double) 远小于管道缓冲区大小。如果你在任何时候读取sizeof(double)字节而只写入sizeof(double)字节,就不会有部分读写。 @JonathanLeffler 哦,是的,你是对的:gnu.org/software/libc/manual/html_node/Pipe-Atomicity.html

以上是关于C管道写入/读取双打序列失败的主要内容,如果未能解决你的问题,请参考以下文章

在这种情况下使用管道会导致 write() 失败吗?

C - 创建/写入/读取命名管道

在 Dataflow 管道中写入 BigQuery 表失败

在C中读取和写入结构到管道

使用 scanf 从管道读取失败

在多个进程写入时读取命名管道