2011-09-02 81 views
0

我正在使用boost::asio::socket实现RTMP协议。我应该使用阻塞还是异步boost :: asio :: socket读/写来实现协议握手

在async_accept之后,协议需要3步握手。请参见下面的代码:

. 
. 
. 
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(); 
} 

RtmpConnection::StartHandshake如果将握手成功(当时RtmpConnection :: HandleData将被调用)返回true,否则为false(连接中断,没有处理还)。

握手有3个主要步骤,每个握手包括Cx和Sx消息,即C{0,1,2},S{0,1,2}

基本握手必须遵循:

// 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. 

正如你可能已经注意到,(像往常一样),握手需要等待。例如,

在发送S0之前,服务器必须等待util C0已经收到。在我们的例子中,C0只包含一个单字节版本的整数,服务器必须验证版本是否有效,然后将S0发送给客户端。

等等,类似于C1/S1,C2/S2(但略有不同)。

我的问题是,我应该使用阻塞读/写进行握手,还是异步?

目前我正在使用阻止读/写,这更容易实现。

然而,我搜索了很多,发现很多人建议异步读/写,因为他们有更好的性能和更多的灵活性。

我在问,如果我想用异步套接字读/写来实现它,我该怎么办?我应该为这3个主要步骤创建一堆处理程序吗?或任何其他更好的建议。

示例伪代码将不胜感激。

回答

0

典型的两种方法是:

  1. 异步。带有少量线程的操作
  2. 同步。每个连接一个线程/客户端

操作我相信这是公认的是在可扩展性方面,(1)击败(2),但在代码的简单性方面(2)通常拍(1)。如果您不希望处理超过几个连接,您可能需要考虑(2)。

可以使用协同程序使异步代码看起来同步,并获得两全其美。但是,没有平台独立的方式,它可能会非常混乱,因为也没有标准的方法来做到这一点。我相信这不是经常做。

使用异步的一种简单方法。操作应该有一个隐式状态机,当有更多数据从套接字读取(或写入)时,将使用回调。这看起来像这样(简化):

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() { /* ... */ } 
}; 

一旦协议变得更加复杂,但这可能不是非常可持续的。为了解决这个问题,在连接类中有一个显式的状态机可能会更简单,并且您只有一个读回调和一个写回调。无论何时写入或读取一些数据,都会根据连接所处的状态执行操作。

+0

感谢您的回复。当然,我必须使用(1),因为我的服务器预计会同时接受100多个连接。每个连接一个线程不能很好地扩展。如果我切换到异步,那么您写的示例代码就是我正在做的事情,但正如您所说,实现起来非常繁琐,将会有很多处理程序,因为握手需要3个步骤。顺便说一句,你能介绍我有关状态机的更多信息,以及如何实现一个读/写回调?谢谢。 –

+0

您可以看看libtorrent中的对等连接对象。 http://libtorrent.svn.sourceforge.net/viewvc/libtorrent/trunk/src/bt_peer_connection.cpp?revision=5948&view=markup请注意,在这里asio上面有点抽象。有发送/接收缓冲区和基本连接对象,用于处理重新启动异步读取和写入操作。由于速率限制支持,代码也有点复杂,即有时它不读或写,因为连接超出了带宽配额。 – Arvid