读取管道(C/C++),没有错误,但不是所有数据

Posted

技术标签:

【中文标题】读取管道(C/C++),没有错误,但不是所有数据【英文标题】:Read pipe (C/C++), no error, but not all data 【发布时间】:2015-11-30 06:39:26 【问题描述】:

在 C++ 程序中,我想获取一些 Python 程序可以轻松提供的数据。 C++ 程序调用popen(),读取数据(一个序列化的protobuf)并继续。这工作正常,但最近开始失败,收到的字符串比发送的短。

我试图理解为什么我没有阅读我所写的内容(尽管没有报告错误)以及如何产生进一步的假设。 Fwiw,这是在linux(64位)上,两个进程都是本地的。 Python 是 2.7。

(数据大小确实变大了(现在是 17MB,曾经是 500 KB),但这不应该导致失败,尽管这是一个确定的信号,我需要为了提高效率进行一些更改。)

在 python 方面,我计算了 group_id 映射到组的字典(RegistrationProgress,参见下文):

payload = RegistrationProgressArray()
for group_id, group in groups.items():
    payload.group.add().CopyFrom(group)
payload.num_entries = len(groups)
print('a, p'.format(a=len(groups), p=len(payload.group)),
      file=sys.stderr)
print(payload.SerializeToString())
print('size=s'.format(s=len(payload.SerializeToString())),
      file=sys.stderr)

请注意,ap 在 python 端匹配(正确!)。大小约为 17MB。在 C++ 方面,

string FetchProtoFromXXXXX<string>(const string& command_name) 
    ostringstream fetch_command;
    fetch_command << /* ... */ ;
    if (GetMode(kVerbose)) 
        cout << "FetchProtoFromXXXXX()" << endl;
        cout << endl << fetch_command.str() << endl << endl;
    
    FILE* fp = popen(fetch_command.str().c_str(), "r");
    if (!fp) 
        perror(command_name.c_str());
        return "";
    
    // There is, sadly, no even remotely portable way to create an
    // ifstream from a FILE* or a file descriptor.  So we do this the
    // C way, which is of course just fine.
    const int kBufferSize = 1 << 16;
    char c_buffer[kBufferSize];
    ostringstream buffer;
    while (!feof(fp) && !ferror(fp)) 
        size_t bytes_read = fread(c_buffer, 1, kBufferSize, fp);
        if (bytes_read < kBufferSize && ferror(fp)) 
            perror("FetchProtoFromXXXXX() failed");
            // Can we even continue?  Let's try, but expect that it
            // may set us up for future sadness when the protobuf
            // isn't readable.
        
        buffer << c_buffer;
    
    if (feof(fp) && GetMode(kVerbose)) 
        cout << "Read EOF from pipe" << endl;
    
    int ret = pclose(fp);
    const string out_buffer(buffer.str());
    if (ret || GetMode(kVerbose)) 
        cout << "Pipe closed with exit status " << ret << endl;
        cout << "Read " << out_buffer.size() << " bytes." << endl;
    
    return out_buffer;

)

大小约为 144KB。

我发送的 protobuf 看起来像这样。 num_entries 有点偏执,因为它应该与group_size() 相同,与group().size() 相同。

message RegistrationProgress  ... 

message RegistrationProgressArray 
required int32 num_entries = 1;
repeated RegistrationProgress group = 2;

那我跑的是

array = FetchProtoFromXXXXX("my_command.py");
cout << "size=" << array.num_entries() << endl;
if (array.num_entries() != array.group_size()) 
    cout << "Something is wrong: array.num_entries() == "
         << array.num_entries()
         << " != array.group_size() == " << array.group_size()
         << " " << array.group().size()
         << endl;
    throw MyExceptionType();

运行它的输出是

122, 122
size=17106774
Read EOF from pipe
Pipe closed with exit status 0
Read 144831 bytes.
size=122
Something is wrong: array.num_entries() == 122 != array.focus_group_size() == 1 1

检查反序列化的 protobuf,看来 group 是一个长度为 one 的数组,仅包含我预期的数组的第一个元素。

【问题讨论】:

【参考方案1】:

这...

buffer << c_buffer;

...要求 c_buffer 包含 ASCIIZ 内容,但在您的情况下,您不是 NUL 终止它。

相反,请确保捕获读取的确切字节数(即使嵌入了NULs):

buffer.write(c_buffer, bytes_read);

【讨论】:

这点是对的,但不解释输出。如果流包含嵌入的'\0' 字节,则通过const string out_buffer(buffer.str());buffer 转换为字符串将在第一个空字节处切断输出。 @chqrlie:看来您只记录了我描述的问题的一半:char c_buffer[kBufferSize]; 在读取后不会终止 NUL,因此堆栈上的任何垃圾字符都将写入@987654329 @ by buffer &lt;&lt; c_buffer;它不仅可能会提前截断。而const string out_buffer(buffer.str());在第一个 NUL 处截断(也许你在想c_str()?)。 你说得对,但你的解释不完整。 OP 调用未定义的行为并且可能会忘记部分输出。 @chqrlie:这是真的 - 我解释了什么是错误的以及如何修复,而不是现有错误代码的所有可能后果,正如我从 OP 的代码中假设他们已经理解的那样.但是,适合自己。 确实,好尴尬。谢谢!【参考方案2】:

您将每个块连接到输出 buffer 与此:

buffer << c_buffer;

正如 Tony D 在他的回答中所解释的那样,您在执行此操作之前不会 null 终止 c_buffer,因此如果 c_buffer 不包含嵌入的空字符,您将调用未定义的行为。

相反,如果c_buffer 确实包含嵌入的空字符,则流的某些部分将被剥离并忽略。

您确定流协议不包含嵌入的'\0' 字节吗?

您还应该阅读Why is “while ( !feof (file) )” always wrong?,尽管在您的情况下,我认为这不会导致您的问题。

【讨论】:

感谢您的链接,这是很好的阅读。 (你说得对,这里没有问题。)

以上是关于读取管道(C/C++),没有错误,但不是所有数据的主要内容,如果未能解决你的问题,请参考以下文章

键盘输入错误地重定向到命名管道读取

命名管道,如何知道在读取端读取的确切字节数。 C++, 视窗

使用 C/C++ 通过管道到/从 Powershell 设置 UTF-8 输入并获取 UTF-8 输出

Win32 匿名管道在第一次读取后损坏

“无法读取未定义的属性”错误消息是不是已更改?

Java中的命名管道和多线程