Плохая производительность boost.ASIO

У меня есть очень простой тест производительности сервера/клиента с использованием boost:: asio в Windows, и, похоже, он работает очень плохо. Я надеюсь, что просто неправильно использую библиотеку, и буду признателен за любые советы.

У меня есть класс сеанса, который записывает длину сообщения, а затем пишет сообщение, а затем ждет, чтобы прочитать длину сообщения, а затем читать сообщение, и продолжает делать это снова и снова без остановок. Однако когда я запускаю его локально на своем компьютере, я получаю невероятно высокую производительность; когда я запускаю сервер на одном компьютере и клиент на другом компьютере, даже в одной и той же сети, производительность снижается, и операция чтения/записи занимает до 1 секунды.

Файл исходного кода сервера выглядит следующим образом:

#include <cstdlib>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
using namespace std;

class Session {
  public:

    Session(io_service& ioService)
      : m_socket(ioService) {}

    tcp::socket& GetSocket() {
      return m_socket;
    }

    void StartRead() {
      m_messageSizeIterator = reinterpret_cast<char*>(&m_messageSize);
      async_read(m_socket, buffer(m_messageSizeIterator, sizeof(m_messageSize)),
        bind(&Session::HandleSizeRead, this, placeholders::error,
        placeholders::bytes_transferred));
    }

    void StartWrite(const char* message, int messageSize) {
      m_messageSize = messageSize;
      m_message = new char[m_messageSize];
      memcpy(m_message, message, m_messageSize);
      async_write(m_socket, buffer(&m_messageSize, sizeof(int)),
        bind(&Session::HandleSizeWritten, this, placeholders::error));
    }

    void HandleSizeRead(const system::error_code& error,
        size_t bytes_transferred) {
      if(!error) {
        m_message = new char[m_messageSize];
        async_read(m_socket, buffer(m_message, m_messageSize),
          bind(&Session::HandleMessageRead, this, placeholders::error,
          placeholders::bytes_transferred));
      } else {
        delete this;
      }
    }

    void HandleMessageRead(const system::error_code& error,
        size_t bytes_transferred) {
      if(!error) {
        cout << string(m_message, m_messageSize) << endl;
        async_write(m_socket, buffer(&m_messageSize, sizeof(int)),
          bind(&Session::HandleSizeWritten, this, placeholders::error));
      } else {
        delete this;
      }
    }

    void HandleSizeWritten(const system::error_code& error) {
      if(!error) {
        async_write(m_socket, buffer(m_message, m_messageSize),
          bind(&Session::HandleMessageWritten, this, placeholders::error));
      } else {
        delete this;
      }
    }

    void HandleMessageWritten(const system::error_code& error) {
      if(!error) {
        delete m_message;
        m_messageSizeIterator = reinterpret_cast<char*>(&m_messageSize);
        async_read(m_socket, buffer(m_messageSizeIterator,
          sizeof(m_messageSize)), bind(&Session::HandleSizeRead, this,
          placeholders::error, placeholders::bytes_transferred));
      } else {
        delete this;
      }
    }

  private:
    tcp::socket m_socket;
    int m_messageSize;
    char* m_messageSizeIterator;
    char* m_message;
};

class Server {
  public:

    Server(io_service& ioService, short port)
        : m_ioService(ioService),
          m_acceptor(ioService, tcp::endpoint(tcp::v4(), port)) {
      Session* new_session = new Session(m_ioService);
      m_acceptor.async_accept(new_session->GetSocket(), bind(&Server::HandleAccept,
        this, new_session,asio::placeholders::error));
    }

    void HandleAccept(Session* new_session, const system::error_code& error) {
      if(!error) {
        new_session->StartRead();
        new_session = new Session(m_ioService);
        m_acceptor.async_accept(new_session->GetSocket(), bind(
          &Server::HandleAccept, this, new_session, placeholders::error));
      } else {
        delete new_session;
      }
    }

  private:
    io_service& m_ioService;
    tcp::acceptor m_acceptor;
};

int main(int argc, char* argv[]) {
  try {
    if(argc != 2) {
      cerr << "Usage: server <port>\n";
      return 1;
    }
    io_service io_service;
    Server s(io_service, atoi(argv[1]));
    io_service.run();
  } catch(std::exception& e) {
    cerr << "Exception: " << e.what() << "\n";
  }
  return 0;
}

И код клиента выглядит следующим образом:

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>

using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
using namespace std;

class Session {
  public:

    Session(io_service& ioService)
      : m_socket(ioService) {}

    tcp::socket& GetSocket() {
      return m_socket;
    }

    void StartRead() {
      m_messageSizeIterator = reinterpret_cast<char*>(&m_messageSize);
      async_read(m_socket, buffer(m_messageSizeIterator, sizeof(m_messageSize)),
        bind(&Session::HandleSizeRead, this, placeholders::error,
        placeholders::bytes_transferred));
    }

    void StartWrite(const char* message, int messageSize) {
      m_messageSize = messageSize;
      m_message = new char[m_messageSize];
      memcpy(m_message, message, m_messageSize);
      async_write(m_socket, buffer(&m_messageSize, sizeof(int)),
        bind(&Session::HandleSizeWritten, this, placeholders::error));
    }

    void HandleSizeRead(const system::error_code& error,
        size_t bytes_transferred) {
      if(!error) {
        m_message = new char[m_messageSize];
        async_read(m_socket, buffer(m_message, m_messageSize),
          bind(&Session::HandleMessageRead, this, placeholders::error,
          placeholders::bytes_transferred));
      } else {
        delete this;
      }
    }

    void HandleMessageRead(const system::error_code& error,
        size_t bytes_transferred) {
      if(!error) {
        cout << string(m_message, m_messageSize) << endl;
        async_write(m_socket, buffer(&m_messageSize, sizeof(int)),
          bind(&Session::HandleSizeWritten, this, placeholders::error));
      } else {
        delete this;
      }
    }

    void HandleSizeWritten(const system::error_code& error) {
      if(!error) {
        async_write(m_socket, buffer(m_message, m_messageSize),
          bind(&Session::HandleMessageWritten, this, placeholders::error));
      } else {
        delete this;
      }
    }

    void HandleMessageWritten(const system::error_code& error) {
      if(!error) {
        delete m_message;
        m_messageSizeIterator = reinterpret_cast<char*>(&m_messageSize);
        async_read(m_socket, buffer(m_messageSizeIterator,
          sizeof(m_messageSize)), bind(&Session::HandleSizeRead, this,
          placeholders::error, placeholders::bytes_transferred));
      } else {
        delete this;
      }
    }

  private:
    tcp::socket m_socket;
    int m_messageSize;
    char* m_messageSizeIterator;
    char* m_message;
};

int main(int argc, char* argv[]) {
  try {
    if(argc != 3) {
      cerr << "Usage: client <host> <port>\n";
      return 1;
    }
    io_service io_service;
    tcp::resolver resolver(io_service);
    tcp::resolver::query query(tcp::v4(), argv[1], argv[2]);
    tcp::resolver::iterator iterator = resolver.resolve(query);
    Session session(io_service);
    tcp::socket& s = session.GetSocket();
    s.connect(*iterator);
    cout << "Enter message: ";
    const int MAX_LENGTH = 1024;
    char request[MAX_LENGTH];
    cin.getline(request, MAX_LENGTH);
    int requestLength = strlen(request);
    session.StartWrite(request, requestLength);
    io_service.run();
  } catch (std::exception& e) {
    cerr << "Exception: " << e.what() << "\n";
  }
  return 0;
}

Любая помощь будет оценена, спасибо.


Для моих целей, отправка действительно очень маленьких сообщений и получение ответов в виртуальном режиме в реальном времени, отключение алгоритма Нэгла оказалось причиной низкой производительности.


person Kranar    schedule 11.01.2010    source источник
comment
Вы исключили возможность того, что ваш маршрутизатор может делать что-то, что вызывает проблему? Какова загрузка ЦП на каждой машине?   -  person bobber205    schedule 11.01.2010
comment
Использование ЦП равно 0 на обеих машинах. Что касается проблемы с маршрутизатором, я написал аналогичную программу без использования ASIO, и она работала очень быстро.   -  person Kranar    schedule 11.01.2010
comment
Начните с тестирования вашей ссылки с помощью iperf. Затем инструментируйте весь процесс — был ли создан сокет? Привязка удалась? Решение сработало? Соединение с сервером работало? Первая отправка сработала? Первый прием работает? Возвращает ли какой-либо сетевой вызов API какие-либо ошибки? Что-то заняло больше времени, чем ожидалось? Посмотрите на сетевой трафик. Есть ли брандмауэр на сервере? Алгоритм Нейгла включен? Сервер долго не отвечал? Есть ли какая-то задержка, которую вы не ожидали в несетевом коде в клиенте?   -  person Permaquid    schedule 11.01.2010
comment
Спасибо за вашу помощь Permaquid. Я просмотрел одно за другим все ваши предложения, и, наконец, проблема заключалась в том, что алгоритм Нэгла был включен, когда для моих целей, отправляющих такие небольшие сообщения и требующих ответов в виртуальном режиме, лучше всего отключить его. Теперь он работает очень хорошо.   -  person Kranar    schedule 11.01.2010
comment
Кранар, какое у тебя сейчас выступление? Просто из интереса.   -  person Carl    schedule 03.08.2011


Ответы (2)


Вы должны отключить алгоритм Nagle. Вызов:

m_socket.set_option(tcp::no_delay(true));

Где это уместно для вашего кода.

person janm    schedule 11.01.2010
comment
+1 за предложение и за ссылку в nagle на вики. Однако хотелось бы увидеть предупреждение о том, что поворот Nagle, вероятно, снизит общую пропускную способность. - person quixver; 20.02.2013
comment
@quixver No nagle приведет к увеличению количества пакетов в сети, если вы отправляете байты с небольшим промежутком между ними. Таймер (например, Nagle) для объединения приведет к меньшему количеству пакетов и, таким образом, улучшит общую пропускную способность сети. Это верно для трафика интерактивной клавиатуры (например, telnet, ssh) и двадцать лет назад составляла большую часть трафика Ethernet. Для связи между программами Nagle приводит к общей более низкой пропускной способности (как это было в исходном вопросе), а не к более высокой пропускной способности. Посмотрите, например, что все сообщение было передано в async_write(), поэтому нет необходимости ждать отправки. - person janm; 20.02.2013

Для моих целей, отправка действительно очень маленьких сообщений и получение ответов в виртуальном режиме в реальном времени, отключение алгоритма Нэгла оказалось причиной низкой производительности.

person Kranar    schedule 11.01.2010