Почему чтение системного вызова прекращает чтение, если отсутствует меньше блока? - PullRequest
0 голосов
/ 12 февраля 2019

Введение и общая цель

Я пытаюсь отправить изображение из дочернего процесса (созданного путем вызова popen из родительского процесса) в родительский процесс.

Изображение представляет собой изображение в градациях серого png.Он открывается библиотекой OpenCV и кодируется с использованием функции imencode той же библиотеки.Таким образом, полученные закодированные данные сохраняются в структуре std::vector типа uchar, а именно в buf в приведенном ниже коде.

Нет ошибок при отправке предварительной информации об изображении

Сначала ребенок отправляет следующую информацию об изображении, необходимую родителю:

  • размер bufвектор, содержащий закодированные данные: этот фрагмент информации необходим для того, чтобы родительский объект выделил буфер того же размера, в который будет записываться информация об изображении, которую он получит от дочернего элемента.Распределение выполняется следующим образом (buf в этом случае - массив, используемый для принятых данных, а не вектор, содержащий закодированные данные):

    u_char *buf = (u_char*)malloc(val*sizeof(u_char));
    
  • количество строк исходного изображения: необходимо дляродительский элемент для декодирования изображения после получения всех данных;
  • количество столбцов исходного изображения: необходим для родительского декодирования изображения после получения всех данных.

Эти данные записываются дочерним элементом в стандартный вывод с использованием cout и считываются родительским с помощью системного вызова fgets.

Эта информация корректно отправляется и принимается, поэтому до сих пор проблем не было .

Отправка данных изображения

Дочерний объект записывает закодированные данные (т.е.данные, содержащиеся в векторе buf), выводятся на стандартный вывод с использованием системного вызова write, в то время как родительский файл использует дескриптор файла, возвращаемый popen, для чтения данных.Данные считываются с помощью системного вызова read.

Запись и чтение данных выполняется в блоках по 4096 байтов внутри циклов while.Строка записи следующая:

written += write(STDOUT_FILENO, buf.data()+written, s);

, где STDOUT_FILENO указывает на запись на стандартный вывод.buf.data() возвращает указатель на первый элемент в массиве, который используется внутренне векторной структурой.written хранит количество байтов, которые были записаны до сих пор, и используется как индекс.s - это количество байтов (4096), которое write будет пытаться отправить каждый раз.write возвращает количество фактически записанных байтов, которое используется для обновления written.

Считывание данных очень похоже и выполняется следующей строкой:

bytes_read = read(fileno(fp), buf+total_bytes, bytes2Copy);

fileno(fp) сообщает, откуда читать данные (fp - файловый дескриптор, возвращаемый popen).buf - это массив, в котором хранятся полученные данные, а total_bytes - это количество прочитанных байтов до сих пор, поэтому оно используется в качестве индекса.bytes2Copy - это число ожидаемых байтов: оно будет BUFLEN (то есть 4096) или для последнего блока данных оставшиеся данные (если, например, общее количество байтов 5000, то после 1 блока4096 байт ожидается еще один блок 5000-4096.

Код

Рассмотрим этот пример.Ниже приведен процесс, запускающий дочерний процесс с popen

#include <stdlib.h>
#include <unistd.h>//read
#include "opencv2/opencv.hpp"
#include <iostream>
#define BUFLEN 4096

int main(int argc, char *argv[])
{
    //file descriptor to the child process
    FILE *fp;
    cv::Mat frame;
    char temp[10];
    size_t bytes_read_tihs_loop = 0;
    size_t total_bytes_read = 0;
    //launch the child process with popen
    if ((fp = popen("/path/to/child", "r")) == NULL)
    {
        //error
        return 1;
    }

    //read the number of btyes of encoded image data
    fgets(temp, 10, fp);
    //convert the string to int
    size_t bytesToRead = atoi((char*)temp);

    //allocate memory where to store encoded iamge data that will be received
    u_char *buf = (u_char*)malloc(bytesToRead*sizeof(u_char));

    //some prints
    std::cout<<bytesToRead<<std::endl;

    //initialize the number of bytes read to 0
    bytes_read_tihs_loop=0;
    int bytes2Copy;
    printf ("bytesToRead: %ld\n",bytesToRead);
    bytes2Copy = BUFLEN;
    while(total_bytes_read<bytesToRead &&
        (bytes_read_tihs_loop = read(fileno(fp), buf+total_bytes_read, bytes2Copy))
    )
    {
        //bytes to be read at this iteration: either 4096 or the remaining (bytesToRead-total)
        bytes2Copy = BUFLEN < (bytesToRead-total_bytes_read) ? BUFLEN : (bytesToRead-total_bytes_read);
        printf("%d btytes to copy\n", bytes2Copy);
        //read the bytes
        printf("%ld bytes read\n", bytes_read_tihs_loop);

        //update the number of bytes read
        total_bytes_read += bytes_read_tihs_loop;
        printf("%lu total bytes read\n\n", total_bytes_read);
    }
    printf("%lu bytes received over %lu expected\n", total_bytes_read, bytesToRead);
    printf("%lu final bytes read\n", total_bytes_read);
    pclose(fp);
    cv::namedWindow( "win", cv::WINDOW_AUTOSIZE );
    frame  = cv::imdecode(cv::Mat(1,total_bytes_read,0, buf), 0);
    cv::imshow("win", frame);

    return 0;

}

, и процесс, открываемый выше, соответствует следующему:

#include <unistd.h> //STDOUT_FILENO
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace std;
using namespace cv;

#define BUFLEN 4096

int main(int argc, char *argv[])
{
    Mat frame;
    std::vector<uchar> buf;
    //read image as grayscale
    frame = imread("test.png",0);
    //encode image and put data into the vector buf
    imencode(".png",frame, buf);
    //send the total size of vector to parent
    cout<<buf.size()<<endl;
    unsigned int written= 0;

    int i = 0;
    size_t toWrite = 0;
    //send until all bytes have been sent
    while (written<buf.size())
    {
        //send the current block of data
        toWrite = BUFLEN < (buf.size()-written) ? BUFLEN : (buf.size()-written);
        written += write(STDOUT_FILENO, buf.data()+written, toWrite);
        i++;
    }
    return 0;

}

Ошибка

Ребенок читает изображение, кодирует его и отправляет сначала размеры (размер, #rows, #cols) родителю, а затем данные закодированного изображения.

Родитель сначала читает размеры (без проблем с этим), затем начинается чтение данных.Данные читаются 4096 байтов на каждой итерации.Однако когда пропущено менее 4096 байт, он пытается прочитать только отсутствующие байты: в моем случае последний шаг должен прочитать 1027 байт (115715%4096), но вместо чтения всех из них он просто читает `15.

Что я напечатал за последние две итерации:

4096 btytes to copy
1034 bytes read
111626 total bytes read

111626 bytes received over 115715 expected
111626 final bytes read
OpenCV(4.0.0-pre) Error: Assertion failed (size.width>0 && size.height>0) in imshow, file /path/window.cpp, line 356
terminate called after throwing an instance of 'cv::Exception'
  what():  OpenCV(4.0.0-pre) /path/window.cpp:356: error: (-215:Assertion failed) size.width>0 && size.height>0 in function 'imshow'

Aborted (core dumped)

Почему read не читает все пропущенные байты?

Я работаю надэто изображение: enter image description here

Могут также быть ошибки в том, как я пытаюсь декодировать обратно изображение, поэтому любая помощь там также будет оценена.

РЕДАКТИРОВАТЬ

На мой взгляд, какВ отличие от некоторых предложений, проблема не связана с наличием \n или \r или \0.

На самом деле, когда я печатаю данные, полученные как целое число со следующими строками:

for (int ii=0; ii<val; ii++)
{
    std::cout<<(int)buf[ii]<< " ";
}

Я вижу 0, 10 и 13 значения (значения ASCII вышеупомянутых символов) в середине данных, так что это заставляет меня думать, что это не проблема.

Ответы [ 3 ]

0 голосов
/ 18 февраля 2019

Обновленный ответ

Я не лучший в мире в C ++, но это работает и даст вам разумную отправную точку.

parent.cpp

#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include "opencv2/opencv.hpp"


int main(int argc, char *argv[])
{
    // File descriptor to the child process
    FILE *fp;

    // Launch the child process with popen
    if ((fp = popen("./child", "r")) == NULL)
    {
        return 1;
    }

    // Read the number of bytes of encoded image data
    std::size_t filesize;
    fread(&filesize, sizeof(filesize), 1, fp);
    std::cout << "Filesize: " << filesize << std::endl;

    // Allocate memory to store encoded image data that will be received
    std::vector<uint8_t> buffer(filesize);

    int bufferoffset   = 0;
    int bytesremaining = filesize;
    while(bytesremaining>0)
    {
        std::cout << "Attempting to read: " << bytesremaining << std::endl;
        int bytesread   = fread(&buffer[bufferoffset],1,bytesremaining,fp);
        bufferoffset   += bytesread;
        bytesremaining -= bytesread;
        std::cout << "Bytesread/remaining: " << bytesread << "/" << bytesremaining << std::endl;
    }
    pclose(fp);

    // Display that image
    cv::Mat frame;
    frame = cv::imdecode(buffer, -CV_LOAD_IMAGE_ANYDEPTH);
    cv::imshow("win", frame);
    cv::waitKey(0);
}

child.cpp

#include <cstdio>
#include <cstdint>
#include <vector>
#include <fstream>
#include <cassert>
#include <iostream>

int main()
{
    std::FILE* fp = std::fopen("image.png", "rb");
    assert(fp);

    // Seek to end to get filesize
    std::fseek(fp, 0, SEEK_END);
    std::size_t filesize = std::ftell(fp);

    // Rewind to beginning, allocate buffer and slurp entire file
    std::fseek(fp, 0, SEEK_SET);
    std::vector<uint8_t> buffer(filesize);
    std::fread(buffer.data(), sizeof(uint8_t), buffer.size(), fp);
    std::fclose(fp);

    // Write filesize to stdout, followed by PNG image
    std::cout.write((const char*)&filesize,sizeof(filesize));
    std::cout.write((const char*)buffer.data(),filesize);
}

Оригинальный ответ

Есть несколько вопросов:

Ваш цикл while пишетданные дочернего процесса неверны:

while (written<buf.size())
{
    //send the current block of data
    written += write(STDOUT_FILENO, buf.data()+written, s);
    i++;
}

Представьте, что ваше изображение имеет размер 4097 байт.Вы будете писать 4096 байтов в первый раз в цикле, а затем попытаться записать 4096 (т.е. s) байтов на втором проходе, когда в вашем буфере останется только 1 байт.

Вы должны написать, какой бы ни былменьше 4096 и байтов осталось в буфере.


Нет смысла отправлять ширину и высоту файла, они уже закодированы в PNG-файле, который вы отправляете.

Там нетНазовите imread() в дочернем файле, чтобы преобразовать файл PNG с диска в cv::Mat, а затем вызовите imencode(), чтобы преобразовать его обратно в PNG для отправки родителю.Просто open() и считайте файл в двоичном виде и отправьте его - это уже файл PNG.


Я думаю, вы должны четко понимать, отправляете ли вы файл PNG или чистый пиксель.данные.Файл PNG будет иметь:

  • заголовок PNG,
  • ширина и высота изображения,
  • дата создания,
  • тип цвета, битовыйглубина
  • сжатые данные пикселей с контрольной суммой

Файл только с данными пикселей будет иметь:

  • RGB, RGB, RGB, RGB
0 голосов
/ 18 февраля 2019
fgets(temp, 10, fp);
...
read(fileno(fp), ...)

Это не может работать.

stdio подпрограммы буферизованы .Буферы контролируются реализацией.fgets(temp, 10, fp); прочитает неизвестное количество байтов из файла и поместит его в буфер.Эти байты никогда не будут видны при низкоуровневом вводе-выводе файла.

Вы никогда и никогда не будете использовать один и тот же файл с обоими стилями ввода-вывода.Либо делайте все с stdio, либо делайте все с низкоуровневым вводом-выводом.Первый вариант самый простой на сегодняшний день, вы просто замените read на fread.

Если по какой-то безбожной причине, известной только злым силам тьмы, вы хотите сохранить оба стиля IO, вы можете попробовать это, позвонив по номеру setvbuf(fp, NULL, _IOLBF, 0), прежде чем делать что-либо еще.Я никогда не делал этого и не могу поручиться за этот метод, но они говорят, что он должен работать.Я не вижу ни единой причины для его использования.

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

 left = data_size;
 total = 0;
 while (left > 0 &&
        (got=read(file, buf+total, min(chunk_size, left))) > 0) {
    left -= got;
    total += got;
 }

 if (got == 0) ... // reached the end of file
 else if (got < 0) ... // encountered an error

Более корректный * - попытаться еще раз, если got < 0 && errno == EINTR, поэтому измененное условие может выглядетькак

 while (left > 0 &&
        (((got=read(file, buf+total, min(chunk_size, left))) > 0) ||
        (got < 0 && errno == EINTR))) {

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

0 голосов
/ 12 февраля 2019

Вы записываете двоичные данные в стандартный вывод, который ожидает текст.Символы новой строки (\n) и / или возвращаемые символы (\r) могут быть добавлены или удалены в зависимости от кодировки вашей системы для конца строки в текстовых файлах.Поскольку вам не хватает символов, похоже, что ваша система удаляет один из этих двух символов.

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

...