Jim Minter
2013-08-26 15:49:34 UTC
Hi all,
I've cobbled together a simple TCP echo service using boost 1.54 asio
and stackless coroutines; source code below. It works for me (Fedora
19, stock gcc 4.8), but I was wondering if anyone can shed light on the
following questions?
Where incoming connections are accepted in do_accept::operator(), I can
use *this as the async completion handler:
yield acceptor->async_accept(new_connection->socket(), *this);
However, this tactic requires that the do_accept class is
copy-constructible, and indeed I can see that the value of 'this' varies
at different points during execution of the operator() coroutine.
In echo_server::operator(), using *this as the completion handler would
cause problems since incoming data is read into one instance of the
class, and is not available when being written later on in the
coroutine. Here I've used:
yield s.async_read_some(buffer(c),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
yield async_write(s, buffer(c, n),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
I'm wondering about the differences in these approaches. Although in
some ways the *this approach looks more readable it seems suboptimal
that the coroutine class must be copy-constructible and confusing that
there should be multiple instances of it in memory. Am I missing a
point: is this a fundamental requirement of coroutine implementation?
If so, should I ditch the boost::bind approach and put my data buffer
behind a shared_ptr?
Many thanks,
Jim Minter
== 8< ==
#include <boost/asio.hpp>
#include <boost/asio/yield.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::bind;
using boost::enable_shared_from_this;
using boost::mem_fn;
using boost::shared_ptr;
class echo_server : public coroutine,
public enable_shared_from_this<echo_server> {
echo_server(io_service &io_service)
: s(io_service) {
}
public:
static shared_ptr<echo_server> create(io_service &io_service) {
return shared_ptr<echo_server>(new echo_server(io_service));
}
tcp::socket &socket() {
return s;
}
void operator()(error_code ec = error_code(), size_t n = 0) {
if(!ec) reenter(this) {
while(1) {
yield s.async_read_some(buffer(c),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
yield async_write(s, buffer(c, n),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
}
}
}
private:
tcp::socket s;
char c[1024];
};
class do_accept : public coroutine {
public:
do_accept(io_service &io_service)
: acceptor(new tcp::acceptor(io_service, tcp::endpoint(tcp::v4(),
7))) {
}
void operator()(error_code ec = error_code(), size_t = 0) {
if(!ec) reenter(this) {
while(1) {
new_connection = echo_server::create(acceptor->get_io_service());
yield acceptor->async_accept(new_connection->socket(), *this);
fork (*new_connection)();
}
}
}
private:
shared_ptr<tcp::acceptor> acceptor;
shared_ptr<echo_server> new_connection;
};
int
main() {
try {
io_service io_service;
(do_accept(io_service))();
io_service.run();
} catch(std::exception &e) {
fprintf(stderr, "%s\n", e.what());
}
return 0;
}
I've cobbled together a simple TCP echo service using boost 1.54 asio
and stackless coroutines; source code below. It works for me (Fedora
19, stock gcc 4.8), but I was wondering if anyone can shed light on the
following questions?
Where incoming connections are accepted in do_accept::operator(), I can
use *this as the async completion handler:
yield acceptor->async_accept(new_connection->socket(), *this);
However, this tactic requires that the do_accept class is
copy-constructible, and indeed I can see that the value of 'this' varies
at different points during execution of the operator() coroutine.
In echo_server::operator(), using *this as the completion handler would
cause problems since incoming data is read into one instance of the
class, and is not available when being written later on in the
coroutine. Here I've used:
yield s.async_read_some(buffer(c),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
yield async_write(s, buffer(c, n),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
I'm wondering about the differences in these approaches. Although in
some ways the *this approach looks more readable it seems suboptimal
that the coroutine class must be copy-constructible and confusing that
there should be multiple instances of it in memory. Am I missing a
point: is this a fundamental requirement of coroutine implementation?
If so, should I ditch the boost::bind approach and put my data buffer
behind a shared_ptr?
Many thanks,
Jim Minter
== 8< ==
#include <boost/asio.hpp>
#include <boost/asio/yield.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::bind;
using boost::enable_shared_from_this;
using boost::mem_fn;
using boost::shared_ptr;
class echo_server : public coroutine,
public enable_shared_from_this<echo_server> {
echo_server(io_service &io_service)
: s(io_service) {
}
public:
static shared_ptr<echo_server> create(io_service &io_service) {
return shared_ptr<echo_server>(new echo_server(io_service));
}
tcp::socket &socket() {
return s;
}
void operator()(error_code ec = error_code(), size_t n = 0) {
if(!ec) reenter(this) {
while(1) {
yield s.async_read_some(buffer(c),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
yield async_write(s, buffer(c, n),
bind(mem_fn(&echo_server::operator()), shared_from_this(), _1, _2));
}
}
}
private:
tcp::socket s;
char c[1024];
};
class do_accept : public coroutine {
public:
do_accept(io_service &io_service)
: acceptor(new tcp::acceptor(io_service, tcp::endpoint(tcp::v4(),
7))) {
}
void operator()(error_code ec = error_code(), size_t = 0) {
if(!ec) reenter(this) {
while(1) {
new_connection = echo_server::create(acceptor->get_io_service());
yield acceptor->async_accept(new_connection->socket(), *this);
fork (*new_connection)();
}
}
}
private:
shared_ptr<tcp::acceptor> acceptor;
shared_ptr<echo_server> new_connection;
};
int
main() {
try {
io_service io_service;
(do_accept(io_service))();
io_service.run();
} catch(std::exception &e) {
fprintf(stderr, "%s\n", e.what());
}
return 0;
}