c++ - 从衍生进程中读取管道时,select() 阻塞后的 read()
问题描述
有 3 个管道调用来为新进程创建标准输入、标准输出和标准错误。调用 fork(),调用 exec()。这包含在一个可以工作的 popen2 函数中。
当使用这个 popen2 函数时,从新进程的 stdout 读取会在 read() 上阻塞,即使在 select() 返回后 read 已准备好读取。我希望它能够读取()。我唯一的猜测是这个 fd 的 read() 试图填充输入 buf。
一个例外:如果 stdin 关闭,stdout 被子进程关闭,即使 buf 无法填充,读取也会完成。
我需要的是 read() 返回准备好阅读的内容?默认模式是缓冲的,我不知道吗?
pid_t popen2(const char *const argv[], int *in, int *out, int *err)
{
int res;
pid_t pid = 0;
int inpipefd[2];
int outpipefd[2];
int errpipefd[2];
if(0!=pipe(inpipefd)) {
perror("allocating pipe for child stdin");
return -1;
}
if(0!=pipe(outpipefd)) {
close(inpipefd[0]);
close(inpipefd[1]);
perror("allocating pipe for child stdout");
return -1;
}
if(0!=pipe(errpipefd)) {
close(inpipefd[0]);
close(inpipefd[1]);
close(outpipefd[0]);
close(outpipefd[1]);
perror("allocating pipe for child stderr");
return -1;
}
pid = fork();
if (0==pid) {
if (-1==dup2(inpipefd[0], STDIN_FILENO)) {exit(errno);}
if (-1==dup2(outpipefd[1], STDOUT_FILENO)) {exit(errno);}
if (-1==dup2(errpipefd[1], STDERR_FILENO)) {exit(errno);}
close(inpipefd[0]);
close(inpipefd[1]);
close(outpipefd[0]);
close(outpipefd[1]);
close(errpipefd[0]);
close(errpipefd[1]);
execvp(argv[0], (char* const*)argv);
perror("exec failed");
exit(1);
}
close(inpipefd[0]);
close(outpipefd[1]);
close(errpipefd[1]);
*in = inpipefd[1];
*out = outpipefd[0];
*err = errpipefd[0];
return pid;
}
...
if(0>=(pid = popen2(argv, &in, &out, &err))) {
return make_unique<std::string>();
}
res = writeall(in, cont.c_str(), cont.length());
if(res==-1) {
goto err;
}
close(in);
...
unique_ptr<std::string> DecryptProcess::Read() {
auto result = make_unique<std::string>();
const unsigned int BUFLEN = 1024*16;
std::vector<char> buf(BUFLEN);
fd_set rfds;
struct timeval tv;
int n;
int fcnt;
FD_ZERO(&rfds);
FD_SET(out_fd, &rfds);
FD_SET(err_fd, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 100000;
fcnt = select(std::max(out_fd, err_fd)+1, &rfds, NULL, NULL, &tv);
if (fcnt == -1) {
return result;
} else if (!fcnt) {
return result;
}
if (FD_ISSET(err_fd, &rfds)) {
n = read(err_fd, &buf[0], buf.size());
}
if (FD_ISSET(out_fd, &rfds)) {
do
{
n = read(out_fd, &buf[0], buf.size());
if (n == -1) {
return result;
}
if (n>0)
result->append(buf.cbegin(), buf.cbegin()+n);
} while ( n > 0 );
}
return result;
}
解决方案
[删除了只会使视图混乱的调试语句]
if (FD_ISSET(out_fd, &rfds)) {
do
{
n = read(out_fd, &buf[0], buf.size());
if (n == -1) {
return result;
}
if (n>0)
result->append(buf.cbegin(), buf.cbegin()+n);
} while ( n > 0 );
}
在这里,您不是在执行单个read()
,而是read()
在循环中执行,直到它返回错误或遇到 EOF。所以代码会循环,直到它消耗所有已经写入管道的数据,然后阻塞,直到有更多的数据写入它。select()
返回一个准备好读取的文件只告诉有一些数据可以从中读取,而不是read()
直到 EOF 才会阻塞。
顺便说一句,不保证select()
即使fd 上标记为准备好读取的单个文件实际上也不会阻塞。[1]确保这一点的唯一方法是将 fd 设置为非阻塞模式(例如 with )并检查错误情况。read()
fcntl(O_NONBLOCK)
errno == EAGAIN
您的代码还有许多其他问题(例如,两次刷新 stdio 缓冲区、无法检查EINTR
等)。
[1]请参阅Linux 手册页的 BUGS 部分。
推荐阅读
- puppet - puppetdb:清除库存命令队列
- python-3.x - SARIMAX 预测(在 Python 中)具有良好的样本内拟合,但预测直接恢复到平均值
- python - 两个向量之间的距离作为 Pandas DataFrame 的列
- vba - VBA.CBlah 和 CBlah 的区别
- sorting - SilverStripe 3:如何按日期排序,月份降序但天数升序
- sql - AWS Athena 中的文件系统上缺少表
- azure - 我们可以使用 azure 通知中心管理徽章计数吗(从面板到批量设备)
- c# - 选择一个包含子类别的类别并将它们放入 DataGridView 中,没有类别重复
- php - 按元素对多维数组进行排序
- python - 在 Lambda 层中处理批次的最佳方法是什么?