Win32 匿名管道在第一次读取后损坏
Posted
技术标签:
【中文标题】Win32 匿名管道在第一次读取后损坏【英文标题】:Win32 anonymous pipe broken after first read 【发布时间】:2015-03-10 11:03:14 【问题描述】:我正在创建一个进程并通过 win32 api 读取它的标准输出和错误。
问题是,在第一次使用ReadFile()
成功读取之后,对于管道中剩余的数据,我总是收到下一个管道错误(109)。 (所以我在第一次调用时只读取了与缓冲区一样大的内容)
我怀疑这可能是因为我正在同步读取管道但是在孩子终止之后?
void read_pipes(std::string &output, HANDLE read_pipe)
assert(read_pipe != nullptr && read_pipe != INVALID_HANDLE_VALUE);
char buffer[4096];
DWORD bytes_left_to_read, err_code; int i = 0;
do
memset(&buffer, 0, 4096);
err_code = 0;
if (!ReadFile(read_pipe, buffer, 4096 - 1, &bytes_left_to_read, NULL))
err_code = GetLastError();
assert(err_code != ERROR_IO_PENDING);
if (err_code != 0 && err_code != ERROR_MORE_DATA)
char sys_err_msg[128];
sys_err_msg[128 - 1] = '\0';
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, err_code, MAKELANGID(0x09, 0x01), sys_err_msg, 127, nullptr);
std::cerr << "Failed to read failed mmdb_importer log, because " << sys_err_msg << ";" << err_code
<< std::endl;
break;
output.append(buffer);
while (true);
以及调用读取的部分代码(完整在http://pastebin.com/vakLULyf下)
if (WaitForSingleObject(mddb_importer_info.hProcess, 60000) == WAIT_TIMEOUT)
//Kill the importer, probably hangs
TerminateProcess(mddb_importer_info.hProcess, EXIT_FAILURE);
GetExitCodeProcess(mddb_importer_info.hProcess, &mddb_importer_return);
//assert (mddb_importer_return != STILL_ACTIVE);
switch (mddb_importer_return)
case EXIT_SUCCESS:
file_to_upload.uploaded = true;
break;
default:
assert(file_to_upload.uploaded == false);
read_pipes(std::ref(file_to_upload.log), mddb_importer_stderr_r);
std::clog << "MDDB_Importer failed with err: : " << file_to_upload.log << std::endl;
read_pipes(std::ref(file_to_upload.log), mddb_importer_stdout_r);
std::clog << "And total size of log " << file_to_upload.log.length() << std::endl;
break;
【问题讨论】:
您没有在ReadFile()
之后检查bytes_left_to_read
的值。也许那里有什么意想不到的东西......
正如@rodrigo 所说,您不是在检查接收了多少数据——也不是空终止缓冲区,这意味着您对string::append
的调用很容易超出缓冲区的末尾.
@rodrigo @jonathan-potter 缓冲区是空终止的,因为我在每个循环开始时将其归零(memset
)并使用缓冲区的大小调用 ReadFile() - 1 . 恕我直言,也不需要检查,因为 ReadFile 返回 false 并将错误号设置为 ERROR_MORE_DATA
。但即便如此,当我将 while 条件更改为 while(bytes_left_to_read)
时,我也有相同的行为。 (而且我知道左边的管道上有文字)
两件事:(1)如果管道处于消息模式,你应该只得到 ERROR_MORE_DATA,它不应该是; (2) 所示代码将在子进程完成写入管道之前终止子进程,除非管道的缓冲区大到足以容纳所有内容。请注意,请求的管道缓冲区大小是建议性的;实际缓冲区大小可能低于预期。
@HarryJohnston 是的,您对 (2) 的看法是正确的,太糟糕了,微软文档从未详细介绍过 (AFAIK)。自己搞定了,不过还是谢谢你:)!
【参考方案1】:
是的,看起来我的排序错误,但由于我不想永远等待一个可能挂起的进程,我现在通过线程在管道中读取我在等待超时之前启动我的 read_pipes 函数,使用标准::异步。 以 Snipett 为例:
if (!CloseHandle(mddb_importer_stderr_w)) throw std::runtime_error("Can not create mddb_importer pipes");
auto err = std::async(std::launch::async, read_pipes, mddb_importer_stderr_r);
auto out = std::async(std::launch::async, read_pipes, mddb_importer_stdout_r);
if (WaitForSingleObject(mddb_importer_info.hProcess, 60000) == WAIT_TIMEOUT)
//Kill the importer, probably hangs
TerminateProcess(mddb_importer_info.hProcess, EXIT_FAILURE);
GetExitCodeProcess(mddb_importer_info.hProcess, &mddb_importer_return);
//assert (mddb_importer_return != STILL_ACTIVE);
const std::string err_string = err.get();
【讨论】:
以上是关于Win32 匿名管道在第一次读取后损坏的主要内容,如果未能解决你的问题,请参考以下文章