为什么缺少少于一个块的读取系统调用会停止读取?

时间:2019-02-12 17:57:35

标签: c++ posix ipc system-calls

简介和一般目标

我正在尝试从子进程(通过从父级调用popen生成)发送图像到父级进程。

该图像是灰度png图像。它使用OpenCV库打开,并使用同一库的imencode函数进行编码。因此,将所得的编码数据存储到类型为std::vector的{​​{1}}结构中,即以下代码中的uchar向量。

发送初步图像信息没有错误

首先,孩子发送父母需要的以下图像信息:

    包含已编码数据的buf向量的
  • 大小:需要此信息,以便父​​级将分配相同大小的缓冲区,以将其将从图像接收的图像信息写入其中。儿童。分配如下执行(在这种情况下,buf是用于接收数据的数组,而不是包含编码数据的向量):

    buf
  • 原始图像的行数:在接收到所有数据之后,父级需要对图像进行解码;
  • 原始图像的列数:在接收到所有数据之后,父级需要对图像进行解码。

这些数据由子级使用u_char *buf = (u_char*)malloc(val*sizeof(u_char)); 写入标准输出,并由父级使用cout系统调用读取。

这些信息已正确发送和接收,因此到目前为止没有问题

发送图像数据

子级使用fgets系统调用将编码数据(即向量buf中包含的数据)写入标准输出,而父级则使用write返回的文件描述符读取数据。使用popen系统调用读取数据。

数据写入和读取在while循环中以read字节的块进行。编写行如下:

4096

其中written += write(STDOUT_FILENO, buf.data()+written, s); 告诉您在标准输出上写。  STDOUT_FILENO返回指向向量结构内部使用的数组中第一个元素的指针。 buf.data()存储到现在为止已写入的字节数,它用作索引。 writtens每次尝试发送的字节数(4096)。 write返回实际已写入的字节数,该字节数用于更新write

数据读取非常相似,它由以下行执行:

written

bytes_read = read(fileno(fp), buf+total_bytes, bytes2Copy); 告诉从哪里读取数据(fileno(fp)fp返回的文件描述符)。 popen是存储接收到的数据的数组,而buf是到目前为止所读取的字节数,因此将其用作索引。 total_bytes是期​​望接收的字节数:bytes2Copy(即BUFLEN)枯萎,或者最后一个数据块剩余的数据(例如,总字节数为{ {1}},然后在4096个字节的1块之后,又将5000的另一个块)。

代码

请考虑以下示例。以下是使用4096

启动子进程的过程
5000-4096

和上面打开的过程对应于以下内容:

popen

错误

孩子读取图像,对其进行编码,然后将尺寸(大小,#行,#cols)发送给父对象,然后再发送编码后的图像数据。

父级首先读取尺寸(没有尺寸),然后开始读取数据。每次迭代都读取#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; } 个字节时,它将尝试仅读取缺少的字节:在我的情况下,最后一步应该读取4096个字节(4096),而不是读取所有它们的读数为15。

我在前两次迭代中打印的是:

1027

115715%4096为什么不读取所有丢失的字节?

我正在处理这张图片:enter image description here

在我尝试对图像进行解码时可能也会出错,因此也将不胜感激。

编辑

在我看来,与某些建议相反,该问题与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\n的存在无关。

实际上,当我用以下几行打印接收为整数的数据时:

\r

我在数据中间看到\0for (int ii=0; ii<val; ii++) { std::cout<<(int)buf[ii]<< " "; } 0值(上述字符的ASCII值),因此我认为这不是问题。 / p>

3 个答案:

答案 0 :(得分:2)

fgets(temp, 10, fp);
...
read(fileno(fp), ...)

这可能行不通。

stdio例程是缓冲的。缓冲区由实现控制。 fgets(temp, 10, fp);将从文件中读取未知数量的字节并将其放入缓冲区。这些字节将再也不会被低级文件IO看到。

您永远都不会在两种IO样式中都使用相同的文件。使用stdio或使用低级IO进行所有操作。到目前为止,第一种选择是最简单的,只需将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))) {

但是这时可读性开始受到影响,您可能希望将其拆分为单独的语句。

答案 1 :(得分:0)

您正在将二进制数据写入标准输出,该输出需要文本。可以添加或删除换行符(\n)和/或返回字符(\r),具体取决于文本文件中行尾的系统编码。由于缺少字符,因此系统似乎正在删除这两个字符之一。

您需要将数据写入以二进制模式打开的文件,并且应该以二进制形式读取文件。

答案 2 :(得分:0)

更新后的答案

我不是世界上最擅长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个字节,然后在缓冲区中仅剩1个字节的情况下尝试在第二遍写入4096个字节(即s)。

您应写入4096和缓冲区中剩余字节中的较小者。


发送文件的宽度和高度没有意义,它们已经被编码在要发送的PNG文件中。

在子级中调用imread()将磁盘上的PNG文件转换为cv::Mat,然后再调用imencode()将其转换回PNG并发送给父级是没有意义的。只需open()并以二进制文件形式读取文件并将其发送-它已经是PNG文件。


我认为您要清楚发送PNG文件还是纯像素数据。 PNG文件将具有:

  • PNG标头
  • 图像的宽度和高度,
  • 创建日期,
  • 颜色类型,位深
  • 压缩的校验和像素数据

仅像素数据文件将具有:

  • RGB,RGB,RGB,RGB