Boost.Asio, tcp::iostream, and multithreading

Viewed 948

tl;dr

How do I make a tcp::iostream based server, as in the code below, accept multiple connections in separate threads?


I am trying my hand at implementing a server / client interface for a preexisting library using Boost.Asio, feeling my way along the examples. (Yes, pre-C++11. Bear with me.)

Rationale for going with tcp::iostream.

Due to circumstances, the whole server process is optional; i.e. when the client is invoked with port = 0, it passes the request to a local Handler instance. In every other case, it connects with the server hopefully listening at that port number, and the server passes the request to a Handler instance of the server process.

(This is all strictly 127.0.0.1. The server exists because there is significant shared state between multiple Handler instances, so if all users would send their requests to one server running on the machine, it would be much easier on memory and start-up times. The "local", port = 0 option exists because that way I can get away with only one client executable for both server-based and local use.)

Anyway. There is boost::asio::ip::tcp::iostream, which makes things really easy:

boost::asio::io_service io_service;

boost::asio::ip::tcp::endpoint endpoint( boost::asio::ip::tcp::v4(), port_ );
boost::asio::ip::tcp::acceptor acceptor( io_service, endpoint );

for (;;)
{
    boost::asio::ip::tcp::iostream stream;
    boost::system::error_code ec;
    acceptor.accept( *stream.rdbuf(), ec );
    if ( ! ec )
    {
        Handler( stream, stream ).run(); // <---- This is the "why"
    }
    else
    {
        throw ec.message();
    }
    stream.close();
}

You see where this is going. The Handler class takes one input stream and one output stream. Instead of passing tcp::iostream, I could pass std::cin / std::cout, or some std::fstream pointing to a request file and a log file. This works very nicely for me.

The big "but"

The problem is that the above code is strictly single-threaded. As the job done by Handler takes significant time (minutes), I need more than one worker thread.

And all the examples and tutorials on "how to make Boost.Asio multithreaded" -- e.g. the HTTP examples server2 (using a pool of individual io_service objects) and server3 (using several threads each calling io_service::run()) -- are going a completely different way (acceptor::async_accept() working on a tcp::socket object contained in a separate connection object).

Blame it on either my confusion about the overall architecture of Boost.Asio, or a lacking documentation, but I cannot figure out how to combine the two approaches, i.e. have multiple worker threads, each handling its connection as a tcp::iostream. There seems to be no way to retrieve a tcp::socket from tcp::iostream, nor a way to feed one to it, at which point I am a bit stumped.

1 Answers

I think I'm subtly missing the question here. It doesn't seen related to Asio:

Live C++03 On Coliru

#define BOOST_THREAD_USES_MOVE
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
#include <boost/asio/basic_socket_iostream.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <list>

struct Handler {
    std::istream& is;
    std::ostream& os;

    Handler(std::istream& is, std::ostream& os) : is(is), os(os){}
    void run() {
        std::cout << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl;
        std::string line;

        while (getline(is, line)) {
            std::reverse(line.begin(), line.end());
            os << line << std::endl;
        }
    }
};

struct SocketRequest : boost::enable_shared_from_this<SocketRequest> {
    boost::asio::ip::tcp::iostream stream;

    void start() {
        boost::async(boost::launch::async, boost::bind(&SocketRequest::do_run, shared_from_this()));
    }

  private:
    void do_run() {
        return Handler(stream, stream).run();
    }
};

int main() {
    boost::asio::io_service io_service;

    unsigned short port_ = 6767;
    boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port_);
    boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);

    for (;;)
    {
        boost::shared_ptr<SocketRequest> req = boost::make_shared<SocketRequest>();

        acceptor.accept(*req->stream.rdbuf());
        req->start();
    }
}

For sample clients:

(for req in HELLO BYE; do sleep 1; netcat 127.0.0.1 6767 <<< "$req"; done)&

Prints

void Handler::run():16
OLLEH
void Handler::run():16
EYB
Related