为什么 read 系统调用在缺少 less than 块时停止读取?
Why read system call stops reading when less than block is missing?
介绍和一般objective
我正在尝试将图像从 child 进程(通过从 parent 调用 popen
生成)发送到 parent 进程。
图像是灰度 png
图像。它使用 OpenCV 库打开,并使用同一库的 imencode
函数进行编码。所以得到的编码数据被存储到uchar
类型的std::vector
结构中,即下面代码中的buf
向量。
发送初步图像信息没有错误
首先child发送parent需要的如下图像信息:
包含编码数据的 buf
向量的大小:需要这条信息,以便 parent 将分配一个相同大小的缓冲区来写入它将从 child 接收到的图像信息。分配按如下方式执行(buf
在这种情况下是用于接收数据的数组而不是包含编码数据的向量):
u_char *buf = (u_char*)malloc(val*sizeof(u_char));
原始图像的行数:parent接收到所有数据后解码图像所需的行数;
原始图像的列数:parent在接收到所有数据后解码图像所需的。
这些数据由 child 使用 cout
在标准输出上写入,并由 parent 使用 fgets
系统调用读取。
这条信息是正确发送和接收的所以到现在没问题。
正在发送图像数据
child 使用 write
系统调用将编码数据(即向量 buf
中包含的数据)写入标准输出,而 parent 使用file-descriptor由popen
返回读取数据。使用 read
系统调用读取数据。
数据写入和读取在 while 循环内以 4096
字节的块为单位执行。行文如下:
written += write(STDOUT_FILENO, buf.data()+written, s);
其中 STDOUT_FILENO
指示在标准输出上写入。
buf.data()
returns 指向向量结构内部使用的数组中第一个元素的指针。
written
存储到现在已经写入的字节数,用作索引。 s
是 write
每次尝试发送的字节数 (4096
)。
write
returns 实际写入的字节数,用于更新 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
块)。
密码
考虑这个例子。以下是启动一个 child 进程的进程 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;
}
错误
child 读取图像,对其进行编码并首先将尺寸(大小、#rows、#cols)发送到 parent,然后是编码图像数据。
parent 首先读取尺寸(没有问题),然后开始读取数据。每次迭代都会读取 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
没有读取所有丢失的字节?
我正在处理这张图片:
我尝试解码回图像的方式也可能存在错误,因此也将不胜感激。
编辑
在我看来,与某些建议相反,问题与 \n
或 \r
或 [=59=]
.
的存在无关
事实上,当我使用以下行打印接收到的整数数据时:
for (int ii=0; ii<val; ii++)
{
std::cout<<(int)buf[ii]<< " ";
}
我在数据中间看到 0
、10
和 13
值(上述字符的 ASCII 值),所以这让我认为这不是问题所在.
您正在将二进制数据写入标准输出,这需要文本。可以添加或删除换行符 (\n
) and/or return 字符 (\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 循环写入来自 child 进程的数据不正确:
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 文件中。
没有必要在 child 中调用 imread()
将 PNG 文件从磁盘转换为 cv::Mat
,然后调用 imencode()
将其转换回 PNG发送到 parent。只需 open()
并将文件读取为二进制文件并发送 - 它已经是一个 PNG 文件。
我想你需要清楚你发送的是PNG文件还是纯像素数据。 PNG 文件将包含:
- PNG header,
- 图片宽度和高度,
- 创建日期,
- 颜色类型,bit-depth
- 压缩、校验和像素数据
一个 pixel-data 唯一的文件将有:
- RGB,RGB,RGB,RGB
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))) {
但此时可读性开始受到影响,您可能希望将其拆分为单独的语句。
介绍和一般objective
我正在尝试将图像从 child 进程(通过从 parent 调用 popen
生成)发送到 parent 进程。
图像是灰度 png
图像。它使用 OpenCV 库打开,并使用同一库的 imencode
函数进行编码。所以得到的编码数据被存储到uchar
类型的std::vector
结构中,即下面代码中的buf
向量。
发送初步图像信息没有错误
首先child发送parent需要的如下图像信息:
包含编码数据的
buf
向量的大小:需要这条信息,以便 parent 将分配一个相同大小的缓冲区来写入它将从 child 接收到的图像信息。分配按如下方式执行(buf
在这种情况下是用于接收数据的数组而不是包含编码数据的向量):u_char *buf = (u_char*)malloc(val*sizeof(u_char));
原始图像的行数:parent接收到所有数据后解码图像所需的行数;
原始图像的列数:parent在接收到所有数据后解码图像所需的。
这些数据由 child 使用 cout
在标准输出上写入,并由 parent 使用 fgets
系统调用读取。
这条信息是正确发送和接收的所以到现在没问题。
正在发送图像数据
child 使用 write
系统调用将编码数据(即向量 buf
中包含的数据)写入标准输出,而 parent 使用file-descriptor由popen
返回读取数据。使用 read
系统调用读取数据。
数据写入和读取在 while 循环内以 4096
字节的块为单位执行。行文如下:
written += write(STDOUT_FILENO, buf.data()+written, s);
其中 STDOUT_FILENO
指示在标准输出上写入。
buf.data()
returns 指向向量结构内部使用的数组中第一个元素的指针。
written
存储到现在已经写入的字节数,用作索引。 s
是 write
每次尝试发送的字节数 (4096
)。
write
returns 实际写入的字节数,用于更新 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
块)。
密码
考虑这个例子。以下是启动一个 child 进程的进程 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;
}
错误
child 读取图像,对其进行编码并首先将尺寸(大小、#rows、#cols)发送到 parent,然后是编码图像数据。
parent 首先读取尺寸(没有问题),然后开始读取数据。每次迭代都会读取 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
没有读取所有丢失的字节?
我正在处理这张图片:
我尝试解码回图像的方式也可能存在错误,因此也将不胜感激。
编辑
在我看来,与某些建议相反,问题与 \n
或 \r
或 [=59=]
.
事实上,当我使用以下行打印接收到的整数数据时:
for (int ii=0; ii<val; ii++)
{
std::cout<<(int)buf[ii]<< " ";
}
我在数据中间看到 0
、10
和 13
值(上述字符的 ASCII 值),所以这让我认为这不是问题所在.
您正在将二进制数据写入标准输出,这需要文本。可以添加或删除换行符 (\n
) and/or return 字符 (\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 循环写入来自 child 进程的数据不正确:
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 文件中。
没有必要在 child 中调用 imread()
将 PNG 文件从磁盘转换为 cv::Mat
,然后调用 imencode()
将其转换回 PNG发送到 parent。只需 open()
并将文件读取为二进制文件并发送 - 它已经是一个 PNG 文件。
我想你需要清楚你发送的是PNG文件还是纯像素数据。 PNG 文件将包含:
- PNG header,
- 图片宽度和高度,
- 创建日期,
- 颜色类型,bit-depth
- 压缩、校验和像素数据
一个 pixel-data 唯一的文件将有:
- RGB,RGB,RGB,RGB
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))) {
但此时可读性开始受到影响,您可能希望将其拆分为单独的语句。