开发者

Should I Use Blocking or Asynchronous boost::asio::socket Read/Write to Implement a Protocol Handshake

开发者 https://www.devze.com 2023-04-02 07:49 出处:网络
I\'m implementing an RTMP protocol using boost::asio::socket. After the async_accept, the protocol requires a 3-step handshake. See the code below:

I'm implementing an RTMP protocol using boost::asio::socket.

After the async_accept, the protocol requires a 3-step handshake. See the code below:

.
.
.
void RtmpServer::StartAsyncAccept()
{
    // Create a new connection
    nextConn = RtmpConnection::Create(this, ios);

// FIXME: shall we use async or blocking accept???
    acceptor.async_accept
    (
        nextConn->GetSocket(),
        boost::bind
        (
            &RtmpServer::HandleAsyncAccept,
            this,
            boost::asio::placeholders::error
        )
    );
}
.
.
.
void RtmpServer::HandleAsyncAccept(const boost::system::error_code& ec)
{
    if (!ec)
    {
        if (nextConn->StartHandshake())
        {
            // Push the current connection to the queue
            AddConnection(nextConn);

            boost::array<char, 0> dummyBuffer;
            nextConn->GetSocket().async_read_some
            (
        // TODO: use a strand for thread-safety.
                boost::asio::buffer(dummyBuffer), // FIXME: Why boost::asio::null_buffers() not working?
                boost::bind
                (
                    &RtmpConnection::HandleData,
                    nextConn,
                    boost::asio::placeholders::error
                )
            );
        }
    }

    // Start to accept the next connection
    StartAsyncAccept();
}

The RtmpConnection::StartHandshake will return true if the handshake succeeded (then RtmpConnection::HandleData will be called), false otherwise (connection aborted, not handled yet).

There are 3 main steps for the handshake, each involves Cx and Sx messages, i.e., C{0,1,2}, S{0,1,2}.

The basic handshake MUST follow:

// HANDSHAKE PROTOCOL
// Handshake Sequence:
//    The handshake begins with the client sending the C0 and C1 chunks.
//
//    The client MUST wait until S1 has been received before sending C2.
//    The client MUST wait until S2 has been received before sending any
//    other data.
//
//    The server MUST wait until C0 has been received before sending S0 and
//    S1, and MAY wait until after C1 as well. The server MUST wait until
//    C1 has been received before sending S2. The server MUST wait until C2
//    has been received before sending any other data.

As you may have noticed that, (as usual), a handshake requires waiting. For example,

The server MUST wait util C0 has been received before sending S0. In our case C0 only contains a one-byte version integer, and the server has to verify if the version is valid or not, t开发者_JAVA百科hen send S0 to the client.

And so on, similar as C1/S1, C2/S2 (but slightly different).

My question is, should I use blocking Read/Write for this handshake, or asynchronous?

Currently I'm using blocking Read/Write, which is easier to implement.

However, I googled a lot, finding out that a lot of guys suggest asynchronous read/write, because they have better performance and more flexibility.

I'm asking if I want to implement it using asynchronous socket read/write, what should I do? Should I create a bunch of handlers for these 3 main steps? or any other better suggestions.

Sample pseudo code will be appreciated.


The typical two approaches are:

  1. async. operations with a small number of threads
  2. sync. operations with one thread per connection/client

I believe it's well established that in terms of scalability, (1) beats (2), but in terms of simplicity of the code (2) typically beats (1). If you don't expect to handle more than a few connections ever, you might want to consider (2).

It's possible to use coroutines to make your asynchronous code look synchronous, and get the best of both worlds. However, there's no platform independent way of doing it, and it might get quite messy, since there's also no standard way to do it. I believe it's not done very often.

One simple way to use async. operations is to have an implicit state machine, based in which callback will be used when there is more data to read from (or write to) the socket. That would look something like this (simplified):

class my_connection {
   tcp::socket m_sock;
   char m_buf[1024];
   int m_pos;

   void async_handshake(size_t bytes_transferred, error_code& ec) {
      if (ec) { ... }
      m_pos += bytes_transferred;

      if (m_pos == handshake_size) {
         parse_handshake();
         return;
      }

      m_sock.async_read(asio::buffer(m_buf + m_pos, handshake_size - m_pos), boost::bind(&async_handshake, shared_from_this(), _1, _2));
   }

   void parse_handshake()
   {
      // ...
      m_pos = 0;
      // ... fill m_buf with handshake ...
      async_write_handshake();
   }

   void async_write_handshake(size_t bytes_transferred, error_code& ec) {
      if (ec) { ... }
      m_pos += bytes_transferred;

      if (m_pos == handshake_size) {
         handshake_written();
         return;
      }

      m_sock.async_write_some(asio::buffer(m_buf + m_pos, handshake_size - m_pos), boost::bind(&async_write_handshake, shared_from_this(), _1, _2));
   }

   void handshake_written() { /* ... */ }
};

This may not be very sustainable once the protocol gets more complicated though. To deal with that, it might be simpler to have an explicit state machine in your connection class, and you have a single read callback, and a single write callback. Whenever some data has been written or read, you perform an action based on which state the connection is in.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号