Периодически данные не доставляются через порт завершения boost::asio/io

Проблема

Я использую boost::asio для проекта где два процесса на одном компьютере взаимодействуют с помощью TCP/IP. Один генерирует данные для чтения другим, но я сталкиваюсь с проблемой, когда данные периодически не отправляются через соединение. Я свел это к очень простому примеру ниже, основанному на пример асинхронного эхо-сервера TCP.

Процессы (исходный код ниже) начинаются нормально, доставляя данные с высокой скоростью от отправителя к получателю. Затем внезапно данные вообще не доставляются в течение примерно пяти секунд. Затем данные доставляются снова до следующей необъяснимой паузы. В течение этих пяти секунд процессы потребляют 0% ЦП, и никакие другие процессы, похоже, ничего особенного не делают. Пауза всегда одинаковая – пять секунд.

Я пытаюсь выяснить, как избавиться от этих киосков и что их вызывает.

Использование ЦП во время всего запуска:

Использование ЦП во время одного запуска

Обратите внимание на три провала использования ЦП в середине выполнения — «выполнение» — это один вызов серверного процесса и клиентского процесса. Во время этих провалов данные не доставлялись. Количество провалов и их время различаются в зависимости от прогона - иногда вообще нет провалов, иногда много.

Я могу повлиять на «вероятность» этих остановок, изменив размер буфера чтения — например, если я сделаю буфер чтения кратным размеру отправляемого фрагмента, эта проблема почти исчезнет. далеко, но не совсем.

Источник и описание теста

Я скомпилировал приведенный ниже код с помощью Visual Studio 2005, используя Boost 1.43 и Boost 1.45. Я тестировал 64-разрядную версию Windows Vista (на четырехъядерном процессоре) и 64-разрядную версию Windows 7 (как на четырехъядерном, так и на двухъядерном процессоре).

Сервер принимает соединение, а затем просто считывает и отбрасывает данные. Всякий раз, когда выполняется чтение, выдается новое чтение.

Клиент подключается к серверу, затем помещает пакет пакетов в очередь отправки. После этого он записывает пакеты по одному. Всякий раз, когда запись завершается, записывается следующий пакет в очереди. Отдельный поток отслеживает размер очереди и выводит его на стандартный вывод каждую секунду. Во время остановок ввода-вывода размер очереди остается точно таким же.

Я пробовал использовать scatter io (запись нескольких пакетов за один системный вызов), но результат тот же. Если я отключу порты завершения ввода-вывода в Boost с помощью BOOST_ASIO_DISABLE_IOCP, проблема исчезнет, ​​но ценой значительного снижения пропускной способности.

// Example is adapted from async_tcp_echo_server.cpp which is
// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Start program with -s to start as the server
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif                      

#include <iostream>
#include <tchar.h>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

#define PORT "1234"
using namespace boost::asio::ip;
using namespace boost::system;

class session {
public:
    session(boost::asio::io_service& io_service) : socket_(io_service) {}

    void do_read() {
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            boost::bind(&session::handle_read, this, _1, _2));
    }

    boost::asio::ip::tcp::socket& socket() { return socket_; }
protected:
    void handle_read(const error_code& ec, size_t bytes_transferred) {
        if (!ec) {
            do_read();
        } else {
            delete this;
        }
    }

private:
    tcp::socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};

class server {
public:
    explicit server(boost::asio::io_service& io_service)
        : io_service_(io_service)
        , acceptor_(io_service, tcp::endpoint(tcp::v4(), atoi(PORT)))
    {
        session* new_session = new session(io_service_);
        acceptor_.async_accept(new_session->socket(),
            boost::bind(&server::handle_accept, this, new_session, _1));
    }

    void handle_accept(session* new_session, const error_code& ec) {
        if (!ec) {
            new_session->do_read();
            new_session = new session(io_service_);
            acceptor_.async_accept(new_session->socket(),
                boost::bind(&server::handle_accept, this, new_session, _1));
        } else {
            delete new_session;
        }
    }

private:
    boost::asio::io_service& io_service_;
    boost::asio::ip::tcp::acceptor acceptor_;
};

class client {
public:
    explicit client(boost::asio::io_service &io_service)
        : io_service_(io_service)
        , socket_(io_service)
        , work_(new boost::asio::io_service::work(io_service))
    {
        io_service_.post(boost::bind(&client::do_init, this));
    }

    ~client() {
        packet_thread_.join(); 
    }

protected:

    void do_init() {
        // Connect to the server
        tcp::resolver resolver(io_service_);
        tcp::resolver::query query(tcp::v4(), "localhost", PORT);
        tcp::resolver::iterator iterator = resolver.resolve(query);
        socket_.connect(*iterator);

        // Start packet generation thread
        packet_thread_.swap(boost::thread(
                boost::bind(&client::generate_packets, this, 8000, 5000000)));
    }

    typedef std::vector<unsigned char> packet_type;
    typedef boost::shared_ptr<packet_type> packet_ptr;

    void generate_packets(long packet_size, long num_packets) {
        // Add a single dummy packet multiple times, then start writing
        packet_ptr buf(new packet_type(packet_size, 0));
        write_queue_.insert(write_queue_.end(), num_packets, buf);
        queue_size = num_packets;
        do_write_nolock();

        // Wait until all packets are sent.
        while (long queued = InterlockedExchangeAdd(&queue_size, 0)) {
            std::cout << "Queue size: " << queued << std::endl;
            Sleep(1000);
        }

        // Exit from run(), ignoring socket shutdown
        work_.reset();
    }

    void do_write_nolock() {
        const packet_ptr &p = write_queue_.front();
        async_write(socket_, boost::asio::buffer(&(*p)[0], p->size()),
            boost::bind(&client::on_write, this, _1));
    }

    void on_write(const error_code &ec) {
        if (ec) { throw system_error(ec); }

        write_queue_.pop_front();
        if (InterlockedDecrement(&queue_size)) {
            do_write_nolock();
        }
    }

private:
    boost::asio::io_service &io_service_;
    tcp::socket socket_;
    boost::shared_ptr<boost::asio::io_service::work> work_;
    long queue_size;
    std::list<packet_ptr> write_queue_;
    boost::thread packet_thread_;
};

int _tmain(int argc, _TCHAR* argv[]) {
    try {
        boost::asio::io_service io_svc;
        bool is_server = argc > 1 && 0 == _tcsicmp(argv[1], _T("-s"));
        std::auto_ptr<server> s(is_server ? new server(io_svc) : 0);
        std::auto_ptr<client> c(is_server ? 0 : new client(io_svc));
        io_svc.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
    return 0;
}

Итак, мой вопрос в основном:

Как мне избавиться от этих прилавков?

Почему это происходит?

Обновление: кажется, существует некоторая корреляция с активностью диска, противоречащая тому, что я сказал выше, поэтому похоже, что если я запущу большую копию каталога на диск во время выполнения теста, это может увеличить частоту ио зависает. Это может означать, что срабатывает приоритет ввода-вывода Windows? Поскольку паузы всегда имеют одинаковую длину, это звучит как тайм-аут где-то в коде ввода-вывода ОС...


person villintehaspam    schedule 10.02.2011    source источник
comment
Я не могу найти его сейчас, но когда я просматривал документацию по ASIO, там было какое-то предупреждение об использовании IOCP с ASIO.   -  person diverscuba23    schedule 10.02.2011
comment
@diverscuba23: Если бы были какие-либо проблемы с использованием IOCP с asio, вероятно, это не был бы метод по умолчанию. В источнике есть комментарий, указывающий на то, что ранее была проблема с GetQueuedCompletionStatus, когда не получались пакеты завершения, если не использовался тайм-аут (в некоторых версиях Windows), может быть, это то, что вы имеете в виду?   -  person villintehaspam    schedule 11.02.2011
comment
это может быть. Как я уже сказал, я не смог найти его, когда вернулся, чтобы найти его, так как это была просто ссылка внизу страницы. Я никогда не удосужился перейти по ссылке, так как это было для Windows, и я делаю свою разработку для систем *NIX.   -  person diverscuba23    schedule 11.02.2011
comment
Я бы предложил запустить на другой платформе, такой как Linux, чтобы увидеть, демонстрирует ли она подобное поведение.   -  person Sam Miller    schedule 11.02.2011


Ответы (2)


  • настроить boost::asio::socket_base::send_buffer_size и Receive_buffer_size
  • настроить max_length на большее число. Поскольку TCP ориентирован на поток, не думайте о нем как о приеме отдельных пакетов. Это, скорее всего, вызывает своего рода "тупик" между окнами отправки/получения TCP.
person PiNoYBoY82    schedule 10.02.2011
comment
Просто слепая настройка SO_RCVBUF и SO_SNDBUF, похоже, не решает проблему, или вы имели в виду что-то особенное? Кроме того, получатель в этом примере вообще не имеет понятия о пакетах, он просто отбрасывает любые данные любого размера, которые он получает. Пожалуйста, объясните, что вы имеете в виду. - person villintehaspam; 11.02.2011
comment
Я думал, что окна отправки и получения заполнялись, вызывая мгновенные пробелы. Когда вы выполняете async_read_some, вы читаете 1024 байта при каждом вызове. Вы пытались увеличить это значение, чтобы оно соответствовало отправляемому вами размеру в 8000 байт? - person PiNoYBoY82; 11.02.2011
comment
@ PiNoYBoY82, да, я тестировал буфер чтения, который кратен размеру вызова записи, и, как указано в вопросе, это почти устраняет проблему. Но, как вы сами указали, на самом деле нет однозначного соответствия между размером записи на одном конце и размером чтения на другом. Также я думаю, что вы смешиваете свой словарный запас, существует разница между размером окна приема TCP и размером буфера отправки/чтения сокета, а также размером буфера чтения, который указан в вызове чтения. - person villintehaspam; 11.02.2011
comment
@ PiNoYBoY82, во время обычных операций отправитель выполняет около 30 тыс. операций записи в секунду, что соответствует примерно 230 тыс. операций чтения в секунду. Я считаю, что размер буфера по умолчанию составляет 8 КБ (хотя я могу ошибаться). Таким образом, в случае, если окно приема или буферы отправки/получения должны быть заполнены, они должны очень быстро очищаться. Конечно, намного быстрее, чем пять секунд. - person villintehaspam; 11.02.2011
comment
хм. Пробовали ли вы дросселировать свои async_writes, чтобы увидеть, проявляется ли проблема по-прежнему? - person PiNoYBoY82; 11.02.2011
comment
Возможно, в handle_read есть ec, который может куда-то вести? - person PiNoYBoY82; 11.02.2011
comment
@ PiNoYBoY82, я не уверен, что понимаю - если ограничение записи повлияет на поведение, что вполне может произойти, какую информацию это даст мне? Так же, если была ошибка в обработчике чтения, то соединение уничтожается, т.е. больше не пишет. - person villintehaspam; 12.02.2011
comment
Я думаю, это указывает на какой-то перерасход, который может потребовать изучения кода IOCP в boost. Меня интересует соотношение вызовов async_write и on_write или невыполненных вызовов по сравнению с завершенными вызовами. Поскольку каждый async_write является незавершенным вызовом IOCP, я полагаю, что существует определенная точка ожидания невыполненных вызовов в очереди, которые может обработать IOCP, что может привести к такому поведению в том, как boost обрабатывает ситуацию. - person PiNoYBoY82; 12.02.2011
comment
Я бы также проверил Tcp1323Opts в реестре technet.microsoft.com/en-us/ библиотека/cc938205.aspx - person PiNoYBoY82; 12.02.2011
comment
@ PiNoYBoY82, существует однозначное сопоставление async_writes с обратными вызовами. У вас не может быть нескольких незавершенных операций записи на сокете. Если вы посмотрите на код в вопросе, вы увидите, что каждая запись будет выполняться в функции обратного вызова только после завершения предыдущей записи. Я не уверен, использовали ли вы асинхронный ввод-вывод с tcp раньше, но поскольку tcp ориентирован на поток, все записи имеют встроенный порядок, поэтому вы просто не можете выполнять несколько операций записи в одном и том же сокете. См. boost.org/doc/ libs/1_45_0/doc/html/boost_asio/ссылка/ - person villintehaspam; 12.02.2011
comment
Ах, да, извините, просто вспоминаю о проблемах с письмом в IOCP. Это звучит как что-то внешнее по отношению к вашему приложению, прерывающее ввод-вывод. Возможно что-то в биосе или другом процессе? Извините за дикие тангеты при устранении неполадок :) - person PiNoYBoY82; 12.02.2011
comment
@ PiNoYBoY82, да, вполне может быть, что это внешнее по отношению к приложению, я обновил вопрос, чтобы указать, что я видел более высокую частоту этих остановок ввода-вывода при одновременной активности диска. Кроме того, не беспокойтесь о диких догадках, иногда они действительно помогают, и любая помощь приветствуется. Вы, вероятно, должны использовать комментарии, а не ответ для этого. - person villintehaspam; 12.02.2011

Недавно я столкнулся с очень похожей проблемой, и у меня есть решение, которое работает для меня. У меня есть асинхронный сервер/клиент, написанный на asio, который отправляет и получает видео (и небольшие структуры запросов), и я наблюдал частые 5-секундные задержки, как вы описываете.

Наше исправление состояло в том, чтобы увеличить размер буферов сокетов на каждом конце и отключить алгоритм Nagle.

pSocket->set_option( boost::asio::ip::tcp::no_delay( true) );
pSocket->set_option( boost::asio::socket_base::send_buffer_size( s_SocketBufferSize ) );
pSocket->set_option( boost::asio::socket_base::receive_buffer_size( s_SocketBufferSize ) );

Возможно, что только один из вышеперечисленных вариантов является критическим, но я не исследовал это дальше.

person Nathan    schedule 03.03.2011