Ошибка синхронизма при отправке данных из файла по каналу

Я сделал этот код:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 255
#define PATH "./test.txt"

int main(int argc, char **argv)
{
    char read_msg[BUFFER_SIZE];
    FILE *fp;
    char buffer[BUFFER_SIZE];
    fp = fopen(PATH, "r");
    int fd[2];
    pid_t pid;
    if(pipe(fd) == -1){
        perror("Pipe failed");
        return 1;
    }

    pid=fork();
    if (pid==-1)
    {
        perror("Fork failed");
        return 1;
    }

    if (pid>0)
    {
        close(fd[0]);
        fgets(buffer, BUFFER_SIZE, (FILE*)fp);
        do{
            write(fd[1],buffer,strlen(buffer)+1); 
            //sleep(1);
        }while(fgets(buffer, BUFFER_SIZE, (FILE*)fp) != NULL);
    close(fd[1]);
    wait(NULL);
}else{
    close(fd[1]);
    while(read(fd[0], read_msg, BUFFER_SIZE) != 0){
    printf("Child had read: %s", read_msg);
    } 
    close(fd[0]);
    }
    return 0;
}

Этот код должен создать канал между родительским и дочерним процессами. Процесс-отец должен прочитать файл и отправить каждую строку дочернему процессу. Дочерний процесс печатает полученные строки.

Но в этом коде есть ошибка. Если я запускаю так, я получаю только несколько строк текста в файле. Если я запускаю с помощью комментария sleep(1), я получаю все строки текста.

Кто-нибудь может увидеть ошибку?


person I'm just a poor boy    schedule 30.03.2016    source источник


Ответы (2)


У вас есть две проблемы.

Здесь:

write(fd[1],buffer,strlen(buffer)+1);

вы пишете завершающий нуль в файл. Когда вы вызываете read(), если доступно несколько строк, он будет считывать их все в ваш буфер, но когда вы затем их printf(), он будет печатать только до первого завершающего нуля, поэтому вы никогда не увидите их ни в одной из строк после первый. Добавление вашего вызова sleep() предотвращает запись в файл более одной строки до того, как другой процесс получит возможность read(), поэтому в этом случае проблема не проявляется.

Решение: не записывайте завершающий нуль в текстовый файл. В то же время вы можете сделать свой код немного компактнее, изменив:

fgets(buffer, BUFFER_SIZE, (FILE*)fp);
do{
    write(fd[1],buffer,strlen(buffer)+1); 
}while(fgets(buffer, BUFFER_SIZE, (FILE*)fp) != NULL);

to:

while ( fgets(buffer, BUFFER_SIZE, fp) ) {
     write(fd[1], buffer, strlen(buffer));
}

Во-вторых, read() не завершает вашу строку нулем, поэтому при реализации этого решения этот вызов:

read(fd[0], read_msg, BUFFER_SIZE)

не будет заканчиваться нулем read_msg. Решение состоит в том, чтобы проверить, сколько байтов read() действительно прочитано, и вручную добавить завершающий нуль после этого количества байтов. Обратите внимание, что вам нужно будет прочитать максимум на один байт меньше размера буфера, если вы его заполните, так как вам понадобится место для завершающего нуля.

Другие комментарии:

  1. Вы не проверяете, удалось ли fopen(), и вы должны это сделать. То же самое для write() и close(). Вы хорошо справляетесь с проверкой возвращаемых значений в других случаях.

  2. Приведение к FILE * в fgets(buffer, BUFFER_SIZE, (FILE*)fp) не имеет никакого эффекта, так как fp уже имеет тип FILE *. Его следует опустить.

person Crowman    schedule 30.03.2016

См. этот пост' принятый ответ для хорошего объяснения того, почему использование здесь fgets может вызвать проблемы.

Вы можете использовать следующий подход в родителях:

   if (pid>0)
{
    int size, file_fd;
    file_fd = fileno(fp);

    close(fd[0]);
    while((size = read(file_fd, buffer, BUFFER_SIZE)) != 0) {
        write(fd[1],buffer,size); 
        //sleep(1);
    }
close(fd[1]);
wait(NULL);
}

А у ребенка:

else{
    close(fd[1]);
    int size;
    while((size = read(fd[0], read_msg, BUFFER_SIZE)) != 0){
        write(1,read_msg, size);
    } 
    close(fd[0]);
    }
    fflush(stdout);
    fclose(fp);
    return 0;
}

Который читается кусками. Обратите внимание, что я заменил:

printf("Child had read: %s", read_msg);

Для согласованности, поскольку мы используем низкоуровневые системные вызовы, и все может стать сложнее, если вы перепутаете буферизованный ввод-вывод, такой как printf, с системными вызовами.

Есть также некоторые другие более мелкие проблемы (например, вы не закрываете родительский файл), и вы должны выполнить гораздо больше проверок при чтении/записи, чтобы убедиться, что не было ошибки, но это доходит до сути вашей проблемы. .

person Bill Harper    schedule 30.03.2016