【问题标题】:c++ connect output stream to input streamc ++将输出流连接到输入流
【发布时间】:2012-09-06 19:38:29
【问题描述】:

我想做的是创建一种“管道”(就像进程之间的管道),但在同一程序内的 c++ iostream 之间。我有一个需要输入流作为参数的函数,但我的数据来自输出流。那么有没有一种标准方法可以将std::ostream 的输出通过管道传输到std::istream 的输入中?

【问题讨论】:

  • std::stringstream 是否适合您的需要?如果不是,请解释原因。
  • 有一个 iostream(注意它的开头有一个i 和一个o)。您在一端泵送数据,然后它就在那里结束。这就是你想要的。

标签: c++ pipe iostream


【解决方案1】:

您可以创建一个std::streambuf,其中输出转到一个缓冲区,当缓冲区满时,std::overflow() 阻塞。另一方面,当缓冲区变空时,您将有一个输入缓冲区阻塞underflow()。显然,阅读和写作将在两个不同的线程中。

棘手的问题是如何同步两个缓冲区:流在访问缓冲区时不使用任何同步操作。只有当任何一个虚函数被调用时,你才能拦截操作并处理同步。另一方面,不使用缓冲区是相当低效的。我解决此问题的方法是使用相对较小的输出缓冲区(例如 256 chars)并覆盖 sync() 以使用此函数将字符传输到输入缓冲区。 streambuf 将使用互斥锁进行同步,并使用条件变量来阻塞输出时的完整输入缓冲区和输入时的空输入缓冲区。为了支持完全关闭,还应该有一个函数设置一个标志,表明没有更多的输入,并且所有进一步的输出操作都应该失败。

创建实际实现表明两个缓冲区是不够的:访问输入缓冲区和输出缓冲区的线程可能在各自的其他缓冲区阻塞时处于活动状态。因此,需要第三个中间缓冲区。通过对上述计划的这个小改动,下面是一些代码(它使用微小的缓冲区来确保存在实际的上溢和下溢;对于实际使用,至少输入缓冲区可能应该更大)。

// threadbuf.cpp                                                      -*-C++-*-
// ----------------------------------------------------------------------------
//  Copyright (C) 2013 Dietmar Kuehl http://www.dietmar-kuehl.de         
//                                                                       
//  Permission is hereby granted, free of charge, to any person          
//  obtaining a copy of this software and associated documentation       
//  files (the "Software"), to deal in the Software without restriction, 
//  including without limitation the rights to use, copy, modify,        
//  merge, publish, distribute, sublicense, and/or sell copies of        
//  the Software, and to permit persons to whom the Software is          
//  furnished to do so, subject to the following conditions:             
//                                                                       
//  The above copyright notice and this permission notice shall be       
//  included in all copies or substantial portions of the Software.      
//                                                                       
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,      
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES      
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND             
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT          
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,         
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING         
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR        
//  OTHER DEALINGS IN THE SOFTWARE. 
// ----------------------------------------------------------------------------


#include <algorithm>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <thread>

// ----------------------------------------------------------------------------

class threadbuf
    : public std::streambuf
{
private:
    typedef std::streambuf::traits_type traits_type;
    typedef std::string::size_type      string_size_t;

    std::mutex              d_mutex;
    std::condition_variable d_condition;
    std::string             d_out;
    std::string             d_in;
    std::string             d_tmp;
    char*                   d_current;
    bool                    d_closed;

public:
    threadbuf(string_size_t out_size = 16, string_size_t in_size = 64)
        : d_out(std::max(string_size_t(1), out_size), ' ')
        , d_in(std::max(string_size_t(1), in_size), ' ')
        , d_tmp(std::max(string_size_t(1), in_size), ' ')
        , d_current(&this->d_tmp[0])
        , d_closed(false)
    {
        this->setp(&this->d_out[0], &this->d_out[0] + this->d_out.size() - 1);
        this->setg(&this->d_in[0], &this->d_in[0], &this->d_in[0]);
    }
    void close()
    {
        {
            std::unique_lock<std::mutex> lock(this->d_mutex);
            this->d_closed = true;
            while (this->pbase() != this->pptr()) {
                this->internal_sync(lock);
            }
        }
        this->d_condition.notify_all();
    }

private:
    int_type underflow()
    {
        if (this->gptr() == this->egptr())
        {
            std::unique_lock<std::mutex> lock(this->d_mutex);
            while (&this->d_tmp[0] == this->d_current && !this->d_closed) {
                this->d_condition.wait(lock);
            }
            if (&this->d_tmp[0] != this->d_current) {
                std::streamsize size(this->d_current - &this->d_tmp[0]);
                traits_type::copy(this->eback(), &this->d_tmp[0],
                                  this->d_current - &this->d_tmp[0]);
                this->setg(this->eback(), this->eback(), this->eback() + size);
                this->d_current = &this->d_tmp[0];
                this->d_condition.notify_one();
            }
        }
        return this->gptr() == this->egptr()
            ? traits_type::eof()
            : traits_type::to_int_type(*this->gptr());
    }
    int_type overflow(int_type c)
    {
        std::unique_lock<std::mutex> lock(this->d_mutex);
        if (!traits_type::eq_int_type(c, traits_type::eof())) {
            *this->pptr() = traits_type::to_char_type(c);
            this->pbump(1);
        }
        return this->internal_sync(lock)
            ? traits_type::eof()
            : traits_type::not_eof(c);
    }
    int sync()
    {
        std::unique_lock<std::mutex> lock(this->d_mutex);
        return this->internal_sync(lock);
    }
    int internal_sync(std::unique_lock<std::mutex>& lock)
    {
        char* end(&this->d_tmp[0] + this->d_tmp.size());
        while (this->d_current == end && !this->d_closed) {
            this->d_condition.wait(lock);
        }
        if (this->d_current != end)
        {
            std::streamsize size(std::min(end - d_current,
                                          this->pptr() - this->pbase()));
            traits_type::copy(d_current, this->pbase(), size);
            this->d_current += size;
            std::streamsize remain((this->pptr() - this->pbase()) - size);
            traits_type::move(this->pbase(), this->pptr(), remain);
            this->setp(this->pbase(), this->epptr());
            this->pbump(remain);
            this->d_condition.notify_one();
            return 0;
        }
        return traits_type::eof();
    }
};

// ----------------------------------------------------------------------------

static void writer(std::ostream& out)
{
    for (std::string line; std::getline(std::cin, line); )
    {
        out << "writer: '" << line << "'\n";
    }
}

// ----------------------------------------------------------------------------

static void reader(std::istream& in)
{
    for (std::string line; std::getline(in, line); )
    {
        std::cout << "reader: '" << line << "'\n";
    }
}

// ----------------------------------------------------------------------------

int main()
{
    try
    {
        threadbuf sbuf;
        std::ostream out(&sbuf);
        std::istream in(&sbuf);

        std::thread write(&::writer, std::ref(out));
        std::thread read(&::reader, std::ref(in));

        write.join();
        sbuf.close();
        read.join();
    }
    catch (std::exception const& ex)
    {
        std::cerr << "ERROR: " << ex.what() << "\n";
    }
}

【讨论】:

  • 努力+1; OP 肯定在寻找更快速、更简单的修复方法。
  • 好吧,使用上面的代码对 snybody 但我来说很快 :) 我以前见过类似的请求,也就是说,它也可能对其他人有用。 ... 实际实施我之前仅概述的内容是一个有趣的练习。最后:我不知道有更简单的解决方法!
  • 你是一个翻转传奇迪特马尔。我在单元测试中使用它,它很有效。谢谢。
  • 一个观察:在我的阅读类析构函数中,我有一个 while 循环,它在阅读线程上执行 timed_join,然后在连接超时时中断,我注意到它有时需要运行该循环两次获得一个干净的出口。否则我会得到 /include/boost/thread/pthread/condition_variable_fwd.hpp:46: boost::condition_variable::~condition_variable(): Assertion `!pthread_mutex_destroy(&internal_mutex)' failed。
  • 一段很棒的代码,但我似乎偶然发现了可能的死锁 while (this->pbase() != this->pptr()) { this->internal_sync(lock);假设有一个读取线程在 underflow()::d_condition.wait(lock) 中等待临时缓冲区为非空。然后调用 close() 并且 close() 获取锁互斥锁。 Close() 然后尝试在循环(internal_sync)中从 outbuffer 填充临时缓冲区。但是如果函数不能在一次调用中清空 outbuffer,close() 会在 while (this->pbase() != this->pptr()) 上无限循环,因为互斥体仍然被持有。
【解决方案2】:

Dietmar Kühl 的帖子非常完整。它提供了一种在线程间执行此操作的方法,做得很好。但是有一个使用boost::iostreams 的简单方法。我今天遇到了这个问题,因为我需要学习如何处理流(设备)和过滤器。我第一次尝试它就成功了。它是您问题的一条线解决方案。

boost::iostreams::copy(istream,ostream);

这是我所在位置的一个工作示例:

#include <iostream>
#include <sstream>
#include <boost/iostreams/copy.hpp>

const char* testz = R"(H4sIAJSHnl4AA61Uy3LCMAw8t19B + wF2QmAoM67ptZ / QU8Y4Alzix9hOH39fhRgnQGd66Qmx
Wq + 0kiZs86Xb2Qf4oKx5fixJ8bjhd / dsB9BshTxiPJsxD876WGuIohFRnECErd / XRmjgb + Jg
7cPs1UjCaEYTC7RQLXc2RC1CBP / SaOEl + e7fEGk1owMj0VMt1fBy + bRaVGWxXpJVWc3nK0bH
ZGJjO1B7YfbncohtYa / M6XW1KJ6KgtEByQQwSXy + KtdrrG + yHr0SzCUvvDNnWyW / a9dtWxUO
MLZj0YrhrTjCJ2yJgYiKA5YYojkqzT2jQ3BGg9udwP43YY57eAeJCi5DMvKyN9QHQ3u / doJD
lNbnrrz9HM0H23kJtXK8IvOqIuW6IAscwohnqrSdwYKMDkHGU034EG2H42pypp + ACrhqFfGc
uLEG0P8EmRJ7 + 06EgIxxEkOLOIQhM45j4vW6Lu4oG2SqARPVTuFFjy8PIBrw9c5bfbmbaeIs
dqvARBcPtYfQtXGiet32n8tP993LJH / pz2jxQpNN7f9TgcmB0RtbPT8dDqOTT8APTbkhEyYE
AAA =)";

int main(){
    std::istringstream source(testz, std::ios_base::in | std::ios_base::binary);

    namespace bio = boost::iostreams;
    bio::copy(source, std::cout);
}

提升流的真正美妙之处在于,您只需添加过滤器即可在流上进行转换。就我而言,以及为什么我留下了很长的testz 字符串,我会在流程中添加一个过滤器,以解压缩数据。

bio::filtering_istreambuf in;
in.push(bio::gzip_decompressor());
in.push(source);
bio::copy(in, std::cout);

好吧,这还不行,我必须弄清楚我需要什么额外的过滤器才能将电子邮件附件转换为合格的 gzip 流。但这是处理流的一种非常优雅的方式。代码在顶部保持简单。我只需要把我的新过滤器推到gzip_decompressor 过滤器前面。

【讨论】:

  • 哇,我问这个已经 8 年了,老实说,从那以后我就没有想过这个问题。但是想出解决方案的荣誉!
猜你喜欢
  • 2010-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-15
  • 1970-01-01
  • 2010-10-02
相关资源
最近更新 更多