繁体   English   中英

为什么缺少不足的块时,读取系统调用会停止读取?

[英]Why read system call stops reading when less than block is missing?

介绍和一般目标

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

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

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

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

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

     u_char *buf = (u_char*)malloc(val*sizeof(u_char)); 
  • 原始图像的行数:在接收到所有数据之后,父级需要对图像进行解码;
  • 原始图像的列数:接收所有数据后,父级需要对图像进行解码。

子级使用cout将这些数据写入标准输出,父级使用fgets系统调用读取这些数据。

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

发送图像数据

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

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

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

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

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

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

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

}

错误

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

父级首先读取尺寸(没有尺寸),然后开始读取数据。 每次迭代读取4096字节的数据。 但是,当缺少少于4096个字节时,它将尝试仅读取缺少的字节:在我的情况下,最后一步应读取1027个字节( 115715%4096 ),但115715%4096读取所有字节, 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所有丢失的字节?

我正在处理此图像: 在此处输入图片说明

我尝试解码回图像的方式也可能会出错,因此也将不胜感激。

编辑

在我看来,与某些建议相反,该问题与\\n\\r\\0的存在无关。

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

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

我看01013中的数据的中间值(上述字符的ASCII值),所以这让我觉得这是没问题的。

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))) {

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

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

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

更新的答案

我不是世界上最擅长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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM