Search code examples
c++boostposixnamed-pipes

Write and read boost into POSIX pipe (No boost.iostream)


I have a setup that uses boost's serialization to archive objects to a file. I want/need to do the same but writing to POSIX pipes instead of basic files.

A simple packet:

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include "AbstractPacket.hpp"

struct SimplePacket final: public AbstractPacket{
    bool value;

    BooleanPacket() noexcept = default;

    template <typename Archive>
    void serialize(Archive& a, const unsigned int) {
        a& boost::serialization::base_object<AbstractPacket>(*this);
        a& value;
    }

    ~SimplePacket() = default;
};

Writing the simple packet:

#include "SimplePacket.hpp"
BOOST_CLASS_EXPORT_GUID(SimplePacket, "SimplePacket")
//...//
void write(Packet* packet){
    std::ofstream file;
    file.open(_client_pipe, std::fstream::out | std::fstream::binary);
    if (file.is_open()) {
        boost::archive::binary_oarchive oa(file);
        oa << packet;
        file.close();
    }
}

This writes the packet (boost serialized archive) to the file. However, I need to write to posix pipes. I have read that using boost.iostream is it possible to open a file stream to a POSIX pipe from its file descriptor. I haven't found a clear way to do this; but I would like to do this without using boost.iostream, is such a thing possible, and if so how could I do it ?


Solution

  • Together, Boost Asio + Process can also do this:

    Using Boost Process

    Live On wandbox

    size_t write(Packet* packet) {
        boost::asio::streambuf sb;
        {
            std::ostream                    os(&sb);
            boost::archive::binary_oarchive oa(os);
            oa << packet;
        }
    
        boost::asio::io_context ctx;
        boost::process::async_pipe p(ctx); // opens a pipe (pair of fds)
    
        size_t bytes = write(p, sb);       // throws system_error on error
    
        return bytes;
    }
    

    If you already have the pipe, use the appropriate constructor for async_pipe to pass the fds.

    Using Boost Asio Only

    Also, if you already have the pipe fd, I'd suggest not using Boost Process at all:

    boost::asio::io_context               ctx;
    boost::asio::posix::stream_descriptor s(ctx, 2); // STDERR
    
    size_t bytes = write(s, sb); // throws system_error on error
    s.release();                 // to avoid close on destruction
    

    In particular, note the release() that might save you time scratching your head.

    Live On Wandbox

    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/serialization/export.hpp>
    
    struct AbstractPacket {
        virtual ~AbstractPacket() = default;
        template <typename Archive> void serialize(Archive&, unsigned) {}
    };
    
    BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPacket)
    
    struct BooleanPacket : public AbstractPacket {
        bool value;
    
        template <typename Archive> void serialize(Archive& a, unsigned) {
            a& boost::serialization::base_object<AbstractPacket>(*this);
            a& value;
        }
    };
    
    BOOST_CLASS_EXPORT(BooleanPacket)
    
    #include <boost/asio.hpp>
    
    size_t write(AbstractPacket* packet) {
        boost::asio::streambuf sb;
        {
            std::ostream                    os(&sb);
            boost::archive::text_oarchive oa(os);
            oa << packet;
        }
    
        boost::asio::io_context               ctx;
        boost::asio::posix::stream_descriptor s(ctx, 2); // STDERR
    
        size_t bytes = write(s, sb); // throws system_error on error
        s.release();                 // to avoid close on destruction
    
        return bytes;
    }
    
    #include <iostream>
    int main() {
        auto test   = new BooleanPacket(); // please use smart pointers
        test->value = true;
    
        std::cout << "Bytes written: " << write(test) << "\n";
    
        delete test; // please use smart pointers
    }
    

    Prints stderr:

    22 serialization::archive 18 1 13 BooleanPacket 1 0
    0 1 0
    1 1
    

    And stdout:

    Bytes written: 62