[英]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)
告诉从哪里读取数据( fp
是popen
返回的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]<< " ";
}
我看0
, 10
和13
中的数据的中间值(上述字符的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文件将具有:
仅像素数据文件将具有:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.