Должен ли я использовать блокировку или асинхронное повышение :: asio :: socket для чтения / записи для реализации протокольного рукопожатия - PullRequest
0 голосов
/ 03 сентября 2011

Я реализую протокол RTMP, используя boost::asio::socket.

После async_accept протокол требует трехэтапного рукопожатия.См. Код ниже:

.
.
.
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 (соединение прервано, еще не обработано).

Существует три основных шага для рукопожатия, каждый из которых включает в себя сообщения 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.

Как вы могли заметить, (как обычно) рукопожатие требует ожидания.Например,

Сервер ДОЛЖЕН подождать, пока утилита C0 была получена перед отправкой S0.В нашем случае C0 содержит только однобайтовое целое число версии, и сервер должен проверить, является ли версия действительной или нет, затем отправить S0 клиенту.

И так далее, аналогично C1 / S1,C2 / S2 (но немного по-другому).

У меня вопрос, должен ли я использовать блокировку чтения / записи для этого рукопожатия или асинхронный?

В настоящее время я использую блокировку чтения / записи, котораяпроще в реализации.

Однако я много гуглил, обнаруживая, что многие парни предлагают асинхронное чтение / запись, потому что они имеют лучшую производительность и большую гибкость.

Я спрашиваючто я должен сделать, используя асинхронное чтение / запись сокетов?Должен ли я создать кучу обработчиков для этих 3 основных шагов?или любые другие лучшие предложения.

Пример псевдокода будет оценен.

1 Ответ

0 голосов
/ 03 сентября 2011

Типичные два подхода:

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

Это может быть не очень устойчивым, когда протокол усложняется. Чтобы справиться с этим, может быть проще иметь явный конечный автомат в вашем классе соединения, и у вас есть один обратный вызов для чтения и один обратный вызов для записи. Всякий раз, когда некоторые данные были записаны или прочитаны, вы выполняете действие в зависимости от того, в каком состоянии находится соединение.

...