Хобрук: Ваш путь к мастерству в программировании

Boost asio завершает работу с кодом 0 без причины. Установка точки останова ПОСЛЕ того, как проблемный оператор решает ее

Пишу пару TCP сервер-клиент с boost asio. Это очень просто и синхронно.

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

Однако в середине получения этих данных в большинстве случаев (около 80%) клиент просто внезапно останавливает рекурсию, всегда перед одним из вызовов чтения (показано ниже). Этого не должно быть, учитывая, что после рекурсии есть несколько других операторов и вызовов функций.

size_t bytes_transferred = m_socket.read_some(boost::asio::buffer(m_fileReadBuffer, m_fileReadBuffer.size()));

m_fileReadBuffer — это boost::массив символов размером 4096 (хотя я пробовал и другие форматы буферов, но безуспешно).

Я совершенно не могу понять, почему это происходит.

  • Программа завершается немедленно, поэтому я не могу передать код ошибки в read_some и прочитать все сообщения об ошибках, так как это должно произойти после оператора read_some.
  • Исключения не выбрасываются
  • Нет ошибок или предупреждений во время компиляции/выполнения
  • Если я ставлю точки останова внутри рекурсивной функции, проблема никогда не возникает (передача завершается успешно)
  • Если я ставлю точки останова после передачи или прерываю выполнение в цикле while после передачи, проблема никогда не возникает, и нет никаких признаков того, что что-то не так.

Также важно отметить, что сервер ВСЕГДА успешно отправляет все данные. Кроме того, проблема всегда возникает в самом конце передачи: я могу отправить 8000 байт, и он завершится, когда будет передано около 6000 или 7000 байт, и я могу отправить 8000000 байт, и он завершится, когда будет что-то вроде 7996000 байт. были переведены.

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

void TCP_Client::receive_volScan_message()
{
    try
    {
        //If the transfer is complete, exit this loop
        if(m_rollingSum >= (std::streamsize)m_fileSize)
        {
            std::cout << "File transfer complete!\n";
            std::cout << m_fileSize << " "<< m_fileData.size() << "\n\n";               

            return;
        }

        boost::system::error_code error;        

        //Transfer isn't complete, so we read some more        
        size_t bytes_transferred = m_socket.read_some(boost::asio::buffer(m_fileReadBuffer, m_fileReadBuffer.size()));

        std::cout << "Received " << (std::streamsize)bytes_transferred << " bytes\n"; 

        //Copy the bytes_transferred to m_fileData vector. Only copies up to m_fileSize bytes into m_fileData
        if(bytes_transferred+m_rollingSum > m_fileSize) 
        {
            //memcpy(&m_fileData[m_rollingSum], &m_fileReadBuffer, m_fileSize-m_rollingSum);
            m_rollingSum += m_fileSize-m_rollingSum;
        }
        else
        {
           // memcpy(&m_fileData[m_rollingSum], &m_fileReadBuffer, bytes_transferred);
            m_rollingSum += (std::streamsize)bytes_transferred;  
        }                     

        std::cout << "rolling sum: " << m_rollingSum << std::endl;

        this->receive_volScan_message();
    }
    catch(...)
    {
        std::cout << "whoops";
    }                
}

В качестве предложения я попытался изменить рекурсивные циклы на циклы for как на клиенте, так и на сервере. Проблема почему-то сохраняется. Единственное отличие состоит в том, что теперь вместо 0 перед ранее упомянутым вызовом read_some он выходит из 0 в конце одного из блоков цикла for, как раз перед началом выполнения другого прохода цикла for.

РЕДАКТИРОВАТЬ: Как оказалось, ошибка не возникает всякий раз, когда я строю клиент в режиме отладки в своей среде IDE.

13.07.2014

  • 'через несколько рекурсивных вызовов' Это звучит неправильно. Почему он должен быть рекурсивным? 13.07.2014
  • Может быть проблема со временем или, возможно, неопределенное поведение? Или, может быть, просто слишком много рекурсий, так что вы заполняете стек, а вызов вызывает переполнение стека, что приводит к сбою программы? 13.07.2014
  • @πάνταῥεῖ Это простой способ сделать это. Функция проверяет, все ли данные были переданы, если нет, то отправляет часть данных и вызывает себя. 13.07.2014
  • @Daniel Для этого достаточно простого цикла. Я все еще не вижу смысла использовать рекурсию. 13.07.2014
  • @JoachimPileborg Я тестировал проблемы со временем с большими задержками между отправкой и получением сообщений, это все равно произошло. Кроме того, я тестирую с небольшими файлами, поэтому обычно бывает 3 или 4 рекурсивных вызова, и такое переполнение наверняка не пройдет незамеченным. 13.07.2014
  • @ πάνταῥεῖ Если я не смогу это исправить, возможным способом действий будет изменение его на цикл. Однако это может быть признаком другой проблемы, которая все еще может присутствовать, поэтому я хотел бы выяснить корень проблемы. 13.07.2014
  • @Daniel Есть хороший шанс, что рекурсия является фактическим источником проблемы: -/ ... 13.07.2014
  • Как вы поддерживаете время жизни массива. Массив должен находиться в памяти до завершения передачи. 13.07.2014
  • Кроме того, убедитесь, что вы каким-то образом ограничиваете свои данные. Ошибка, которую я совершил, заключалась в том, что я продолжал помещать данные в буфер ядра с некоторыми записями, не дожидаясь, пока клиент наверстает упущенное. В итоге у меня закончилась память. 13.07.2014
  • @nishantjr m_fileReadBuffer является переменной-членом класса, а функция, рекурсивно вызываемая для чтения, является функцией-членом класса. Это не должно быть проблемой. Кроме того, я делал это раньше, и это очень громкая ошибка, а не выход из 0 из ниоткуда. Что касается идеи дросселирования: я попытался добавить 1-секундную задержку между пакетами, и я вижу, что этого времени более чем достаточно для клиента. Как только один из последних пакетов отправлен и клиент его прочитает, он выходит из нуля. 13.07.2014
  • Read_some является синхронным. Это не будет работать у вас io_service.run 13.07.2014
  • Как вы определяете конец передачи? Байты == 0 не будут работать 13.07.2014
  • @nishantjr Я веду скользящую сумму, которая добавляет bytes_transferred с каждым вызовом. Когда эта сумма равна ›= ожидаемому размеру файла, он обнаруживает конец передачи. Я могу гарантировать, что скользящая сумма и размер файла также рассчитываются правильно. 13.07.2014
  • Можете ли вы опубликовать функцию рекурсивного чтения. (Я чувствую, что это действительно должен быть простой цикл for) 13.07.2014
  • Я отредактировал основной пост с функцией рекурсивного чтения. Оглядываясь назад, действительно кажется, что цикл for должен быть лучше. Переменная скользящей суммы и то, как заканчивается рекурсия, по сути являются большим циклом for. 13.07.2014
  • Где обновляется m_FileSize? Условие if в конце кажется излишним. Старайтесь избегать дублирования данных (m_fileSize и m_fileData.size()) 13.07.2014
  • m_fileSize уже установлен до вызова первого экземпляра этой рекурсивной функции. Он имеет полный размер файла 13.07.2014
  • Хм, не уверен, что происходит. Это не проблема с вызовами asio. Чего-то не хватает. Похоже на логическую ошибку в логике счетчика или проверки завершения, а не в вызовах asio, но я не могу ее найти. 13.07.2014
  • Я изменил рекурсивные вызовы функций на цикл for, но та же самая проблема сохраняется. В случайные моменты времени, когда он пытается выполнить чтение, ему каким-то образом удается выйти из всей программы с кодом 0. 13.07.2014

Ответы:


1

Я не совсем понял проблему, однако мне удалось ее полностью исправить.

Корень проблемы заключался в том, что на клиенте вызовы boost::asio::read выполняли основной выход с кодом 0, если сообщения сервера еще не поступали. Это означает, что простой

while(m_socket.available() == 0)
{
    ;
}  

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

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

Я думаю, что несоответствие отладки/выпуска произошло из-за того, что m_readBuffer не был инициализирован ничем всякий раз, когда происходили вызовы чтения. Это привело к тому, что вызов read возвращал какую-то скрытую ошибку. При отладке неинициализированные переменные автоматически устанавливаются в NULL, незаметно устраняя мою проблему.

Я понятия не имею, почему добавление цикла while после передачи предотвратило проблему. Ни почему это обычно происходило в конце передачи, после того как m_readBuffer был установлен и успешно использован несколько раз.

Кроме того, я никогда раньше не видел такого типа «сбоя», когда программа просто завершает работу с кодом 0 в случайном месте, без каких-либо ошибок или исключений.

13.07.2014
  • Это не похоже на правильное решение. Как ваш клиент узнает, сколько данных ожидать от сервера? 16.07.2014
  • @SamMiller Файл всегда имеет заголовок постоянного размера, который содержит такую ​​информацию, как размер файла. Вызов while(m_socket.available() == 0) происходит, пока клиент ожидает чтения этого заголовка, и еще раз, пока он ждет чтения содержимого файла (которое приходит сразу после этого). Я до сих пор не понимаю, почему функции чтения не блокируются естественным образом, пока не появятся данные для чтения, как они должны 16.07.2014
  • Функции чтения действительно выполняют блокирующие операции в соответствии с запросом, я подозреваю, что ваша проблема заключается в другом, поскольку вы не опубликовали sscce. 16.07.2014
  • Новые материалы

    Создание успешной организации по науке о данных
    "Рабочие часы" Создание успешной организации по науке о данных Как создать эффективную группу по анализу данных! Введение Это обзорная статья о том, как создать эффективную группу по..

    Технологии и проблемы будущей работы
    Изучение преимуществ и недостатков технологий в образовании В быстро меняющемся мире технологии являются решающим фактором в формировании будущего работы. Многие отрасли уже были..

    Игорь Минар из Google приедет на #ReactiveConf2017
    Мы рады сообщить еще одну замечательную новость: один из самых востребованных спикеров приезжает в Братиславу на ReactiveConf 2017 ! Возможно, нет двух других кланов разработчиков с более..

    Я собираюсь научить вас Python шаг за шагом
    Привет, уважаемый энтузиаст Python! 👋 Готовы погрузиться в мир Python? Сегодня я приготовил для вас кое-что интересное, что сделает ваше путешествие более приятным, чем шарик мороженого в..

    Альтернатива шаблону исходящих сообщений для архитектуры микросервисов
    Познакомьтесь с двухэтапным сообщением В этой статье предлагается альтернативный шаблон для папки Исходящие : двухэтапное сообщение. Он основан не на очереди сообщений, а на..

    React on Rails
    Основное приложение Reverb - это всеми любимый монолит Rails. Он отлично обслуживает наш API и уровень просмотра трафика. По мере роста мы добавляли больше интерактивных элементов..

    Что такое гибкие методологии разработки программного обеспечения
    Что представляют собой гибкие методологии разработки программного обеспечения в 2023 году Agile-методологии разработки программного обеспечения заключаются в следующем: И. Введение A...