【问题标题】:Should I Use Blocking or Asynchronous boost::asio::socket Read/Write to Implement a Protocol Handshake我应该使用阻塞还是异步 boost::asio::socket 读/写来实现协议握手
【发布时间】:2011-09-02 22:43:29
【问题描述】:

我正在使用 boost::asio::socket 实现 RTMP 协议。

在 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 将返回 true(然后将调用 RtmpConnection::HandleData),否则返回 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 个主要步骤创建一堆处理程序吗?或任何其他更好的建议。

示例伪代码将不胜感激。

【问题讨论】:

    标签: c++ sockets boost asynchronous boost-asio


    【解决方案1】:

    典型的两种方法是:

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

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

    【讨论】:

    • 感谢您的回复。当然,我必须使用(1),因为我的服务器预计会同时接受超过 100 个连接。每个连接一个线程不能很好地扩展。你写的示例代码是我目前打算做的,如果我切换到异步,但是正如你所说,实现起来非常繁琐,会有很多处理程序,因为握手需要3个步骤。顺便说一句,你能给我介绍更多关于状态机的信息,以及如何实现单个读/写回调吗?谢谢。
    • 您可以查看 libtorrent 中的对等连接对象。 libtorrent.svn.sourceforge.net/viewvc/libtorrent/trunk/src/… 请注意,这里在 asio 之上还有一些抽象。有发送/接收缓冲区和处理重新启动异步读写的基本连接对象。由于支持速率限制,代码也有点复杂,即有时它不会读取或写入,因为连接超出了带宽配额。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-14
    • 1970-01-01
    • 2011-09-02
    • 2016-08-03
    • 1970-01-01
    相关资源
    最近更新 更多