为什么读取系统调用会在缺少块时停止读取?

问题描述 投票:0回答:3

介绍和总体目标

我正在尝试从子进程发送图像(通过从父进程调用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字节块中执行的。写作行如下:

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

其中STDOUT_FILENO告诉我在标准输出上写。 buf.data()返回指向矢量结构内部使用的数组中第一个元素的指针。 written存储到目前为止已写入的字节数,并将其用作索引。 s4096每次尝试发送的字节数(write)。 write返回实际写入的字节数,用于更新written

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

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

fileno(fp)告诉从哪里读取数据(fppopen返回的文件描述符)。 buf是存储接收数据的数组,total_bytes是到目前为止读取的字节数,因此它被用作索引。 bytes2Copy是预期接收的字节数:它是BUFLEN(即4096)或最后一个数据块枯萎的剩余数据(如果例如总字节数为5000则在4096字节的1个块之后另一个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;

}

错误

孩子读取图像,对其进行编码并首先将尺寸(大小,#,#,#)发送给父母,然后发送编码图像数据。

父母首先读取尺寸(没有概率),然后它开始读取数据。在每次迭代时读取数据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]<< " ";
}

我在数据中间看到qazxsw poi,qazxsw poi和qazxsw poi值(上面提到的字符的ASCII值),所以这让我觉得它不是问题。

c++ posix ipc system-calls
3个回答
2
投票
0

这不可行。

10例程被缓冲。缓冲区由实现控制。 13将从文件中读取未知数量的字节并将其放入缓冲区。低级文件IO永远不会再看到这些字节。

您永远不会对两种样式的IO使用相同的文件。要么使用fgets(temp, 10, fp); ... read(fileno(fp), ...) 做任何事情,要么用低级IO做任何事情。第一个选项是迄今为止最简单的选择,你只需用stdio替换fgets(temp, 10, fp);

如果对于一些不明智的原因只知道黑暗的邪恶力量你想保留两种风格的IO,你可以通过在做任何其他事情之前调用stdio来尝试。我从来没有这样做,也不能保证这种方法,但他们说它应该有用。我没有看到使用它的一个原因。

在一个可能不相关的注释中,您的读取循环在其终止条件中有一些逻辑,这些逻辑不容易理解并且可能无效。读取文件的常规方法大致如下:

read

更正确的方法是再次尝试fread,所以修改后的条件可能看起来像

setvbuf(fp, NULL, _IOLBF, 0)

但此时可读性开始受损,您可能希望将其拆分为单独的语句。


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),具体取决于文本文件中行尾的系统编码。由于您缺少字符,系统似乎正在删除这两个字符中的一个。

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


0
投票

更新的答案

我不是C ++的世界上最好的,但是这有效并且会给你一个合理的起点。

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

\n

\r

parent.cpp

原始答案

有几个问题:

从子进程写入数据的while循环不正确:

#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);
}

想象一下你的图像是4097字节。您将在循环中第一次写入4096个字节,然后在缓冲区中只剩下1个字节时尝试在第二个传递中写入4096(即child.cpp)字节。

你应该写出4096中较小者和缓冲区中剩余的字节。


发送文件的宽度和高度毫无意义,它们已经在您发送的PNG文件中编码。

没有必要在孩子中调用#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); } 将PNG文件从磁盘转换为while (written<buf.size()) { //send the current block of data written += write(STDOUT_FILENO, buf.data()+written, s); i++; } ,然后调用s将其转换回PNG以发送给父母。只需imread()并将文件读为二进制文件并发送 - 它已经是一个PNG文件。


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

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

仅像素数据文件将具有:

  • RGB,RGB,RGB,RGB
© www.soinside.com 2019 - 2024. All rights reserved.