[Boost-users] Boost.ASIO "deasynchronisation"

44 views
Skip to first unread message

Igore Dmit.

unread,
Feb 25, 2010, 6:56:37 PM2/25/10
to boost...@lists.boost.org
Hello,
I'm developing networking library that provides RTMP-like networking using Boost.ASIO library. My problem is that any synchronous-access function breaks into many little functions that do some part of job and invoke async operation with "next" function as a handler, it makes the code really ugly and obfuscating.
E.g. void hadshake that reads N bytes from peer, responds with those bytes + same count of other bytes, reads N bytes back and checks that they are the same. It breaks into:

  void start_handshake_wait();
  void handle_handshake_wait(const boost::system::error_code&, std::size_t, buffer_ptr &);
  void handle_handshake_wait_write_dup(..same as above..);
  void handle_handshake_wait_write_mine(...);
  void handle_handshake_wait_read_mine_check(...);

Every function uses boost::bind, so for some quite simple operation a lot of code is produced. Receiving a chunk of data breaks into:

  void read_header_head(); // header size depends on first byte
  void parse_header_head();
  void parse_header_body(); 
  void parse_chunk_body();

The only idea I came to is to use some tricks like coroutines. (Sorry for big listings)

template<class T>
class Context : public boost::enable_shared_from_this< Context<T> >
{
public:
    typedef boost::shared_ptr<Context<T> > ptr;

private:
    typedef boost::shared_ptr<T> objptr_t;

    typedef void (T::*funcptr_t)
        (const BSYSECode&, std::size_t, Context<T>::ptr, buffer_ptr);

    objptr_t objptr_;
    funcptr_t funcptr_;
public:
    std::map< std::string, buffer_ptr > buffers;
    int state;

    Context(objptr_t op, funcptr_t fp)
      : objptr_(op), funcptr_(fp),
        buffers(), state(0)
    { }

    template<typename AsyncReadStream>
    void async_read(AsyncReadStream &s, buffer_ptr buf)
    {
        boost::asio::async_read(s, *buf, boost::bind(funcptr_, objptr_,
                                BASIOPErr, BASIOPBytes,
                                this->shared_from_this(), buf));
    }

    template<typename AsyncWriteStream>
    void async_write(AsyncWriteStream &s, buffer_ptr buf)
    {
        boost::asio::async_write(s, *buf, boost::bind(funcptr_, objptr_,
                                 BASIOPErr, BASIOPBytes,
                                 this->shared_from_this(), buf));
    }
};

#define CTX_PROLOG(c)                  \
   if(!c)                              \
       return;                         \
                                       \
   if(c->state == 0) {                 \


#define CTX_READ(c, s, b)
             \
       c->state=__LINE__;              \
       c->async_read(s, b);            \
       return;                         \
   } else if(c->state == __LINE__) {   \

#define CTX_WRITE(c, s, b)
            \
       c->state=__LINE__;              \
       c->async_write(s, b);           \
       return;                         \
   } else if(c->state == __LINE__) {   \


#define CTX_EPILOG
                    \
   }

buffer_ptr is actually a shared_ptr to buffer, it's used to get buffer back to pool as it goes out of scope. With all this handshake becomes something more readable (to me, but it still cryptic; again, sorry for posting that much):

class Handshaker :
    public boost::enable_shared_from_this<Handshaker>
{
private:
    Connection::ptr conn_;

public:
    typedef boost::shared_ptr<Handshaker> ptr;
    typedef Context<Handshaker> context;

    Handshaker(Connection::ptr conn)
      : conn_(conn)
    { }

    void shake() {
        context::ptr ctx(new context(shared_from_this(), &Handshaker::perform));
        perform(BSYSECode(), 0, ctx, buffer_ptr());
    }

private:
    void perform(const BSYSECode&  err,  std::size_t  sz,
                 context::ptr      ctx,  buffer_ptr   buf)
    {
        if(err)
            return;

        CTX_PROLOG(ctx);
        CTX_READ(ctx, conn_->socket(), conn_->pool()->allocate(HandshakeSize));
        CTX_WRITE(ctx, conn_->socket(), buf);

        ctx->buffers["mybuf"] = conn_->pool()->allocate(HandshakeSize);
        CTX_WRITE(ctx, conn_->socket(), ctx->buffers["mybuf"]);

        CTX_READ(ctx, conn_->socket(), conn_->pool()->allocate(HandshakeSize));

        buffer_ptr mybuf = ctx->buffers["mybuf"];
        buffer_ptr hisbuf = buf;
        if(boost::asio::buffer_size(*mybuf) != boost::asio::buffer_size(*hisbuf))
            return;

        if(memcmp(  boost::asio::buffer_cast<void*>(*mybuf),
                    boost::asio::buffer_cast<void*>(*hisbuf),
                    boost::asio::buffer_size(*mybuf)          ))
            return;

        CTX_EPILOG;
    }
};

Finally, the question: is there any library that provides functionality like that? I'd like to stick with it cause it looks like my implementation is really bad, HUGE bunch of limitations on code written that way, cryptic look, usage of macro, any local variable has to resort in ctx to survive async call.

Nat Goodspeed

unread,
Feb 25, 2010, 9:06:57 PM2/25/10
to boost...@lists.boost.org
Igore Dmit. wrote:

> My problem is that any synchronous-access
> function breaks into many little functions that do some part of job and
> invoke async operation with "next" function as a handler, it makes the
> code really ugly and obfuscating.

I know exactly what you mean. We've started using the proposed
Boost.Coroutine library (not yet official, a Google Summer of Code
effort plus some bug fixes). You might be interested that the original
author specifically targets use with ASIO.

http://www.crystalclearsoftware.com/soc/coroutine/index.html
http://www.crystalclearsoftware.com/soc/coroutine/coroutine/asio.html
http://www.boostpro.com/vault/index.php?action=downloadfile&filename=boost-coroutine-2009-12-01.tar.gz&directory=Concurrent%20Programming&

A couple of the bundled tests are built around ASIO.
_______________________________________________
Boost-users mailing list
Boost...@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users

Anthony Foiani

unread,
Feb 25, 2010, 9:31:37 PM2/25/10
to boost...@lists.boost.org

>>>>> "Igore" == Igore Dmit <true...@gmail.com> writes:

Igore> My problem is that any synchronous-access function breaks into
Igore> many little functions that do some part of job and invoke async
Igore> operation with "next" function as a handler, it makes the code
Igore> really ugly and obfuscating.

>>>>> "Nat" == Nat Goodspeed <n...@lindenlab.com> writes:

Nat> I know exactly what you mean. We've started using the proposed
Nat> Boost.Coroutine library (not yet official, a Google Summer of
Nat> Code effort plus some bug fixes). You might be interested that
Nat> the original author specifically targets use with ASIO.

The main author of ASIO itself is also a big proponent of using
coroutines, and he demonstrates some tricks for using them cleanly on
top of ASIO:

http://blog.think-async.com/2009/07/wife-says-i-cant-believe-it-works.html
http://blog.think-async.com/2009/08/secret-sauce-revealed.html
http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html

One more option to investigate...

Happy Hacking,
t.

Rutger ter Borg

unread,
Feb 26, 2010, 5:44:36 AM2/26/10
to boost...@lists.boost.org
Anthony Foiani wrote:

> The main author of ASIO itself is also a big proponent of using
> coroutines, and he demonstrates some tricks for using them cleanly on
> top of ASIO:
>
> http://blog.think-async.com/2009/07/wife-says-i-cant-believe-it-
works.html
> http://blog.think-async.com/2009/08/secret-sauce-revealed.html
> http://blog.think-async.com/2009/08/composed-operations-coroutines-and-
code.html
>

Also see the new http example #4 in Boost 1.42,

Cheers,

Rutger

Reply all
Reply to author
Forward
0 new messages