Arhn - архитектура программирования

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

Очень кратко резюмировано:

Я реализую простой TCP-сервер с использованием boost :: asio, который позволяет принимать новые соединения без блокировки. Внутри логики обработки нового соединения будет выполняться много работы, которая может занять до нескольких минут. В частности, я собираюсь запустить новый процесс и дождаться его завершения при чтении stdin, stderr и его кода возврата. Просто образ Я хочу запускать g ++ для каждого соединения, компилировать несколько исходных файлов и получать информацию о сборке. Хотя g ++ работает в отдельном процессе, я все еще хочу иметь возможность принимать новые соединения.

Я новичок в поддержке :: asio и ищу несколько советов по дизайну и поддержку моих текущих идей.

Вариант №1: использование резьбы для каждого соединения и отсоединение его

#include <boost/asio.hpp>

#include "tcp_connection.hpp"
#include <thread>
#include <functional>

using boost::asio::ip::tcp;

class tcp_server
{
public:
  tcp_server(boost::asio::io_context& io_context, unsigned short port_num)
   : m_io_context(io_context),
     m_acceptor(io_context, tcp::endpoint(tcp::v4(), port_num)),
     m_port_num(port_num)
  {
      // create initial connection that will be accepted
      create_connection();
  }

private:
    void create_connection()
    {
        // create new connection that will be accepted
        tcp_connection::pointer new_connection = tcp_connection::create(m_io_context);
        // can't mix std::bind with boost::asio::placeholders ...
        m_acceptor.async_accept(new_connection->socket(),
                                boost::bind(&tcp_server::handle_accept, this,
                                          boost::asio::placeholders::error));

        // save new connection to be handled next
        m_curr_connection = new_connection;
    }


    void handle_accept(const boost::system::error_code& error)
    {
        if(!error)
        {
            // run new connection in own thread 
            std::thread t(std::bind(&tcp_connection::run, m_curr_connection));
            // create next connection that will be accepted
            create_connection();
            // detach thread before it goes out of scope 
            t.detach();
        }
    }

    boost::asio::io_context& m_io_context;
    tcp::acceptor m_acceptor;
    tcp_connection::pointer m_curr_connection;
    unsigned short m_port_num;
};

Таким образом, принятие соединения выполняется асинхронно в основном потоке с использованием async_accept. Для обработки я создаю рабочий поток, который вызывает g ++ и ждет его завершения. Тем не менее, сервер тем временем может принимать новые соединения и запускать новые компиляции.

Метод запуска соединения похож на этот без обработки ошибок.

auto prog = boost::process::search_path("g++");
boost::asio::io_context io_context;

std::future<std::string> data;
boost::process::child processCobol(prog, "main.cpp"
                                   boost::process::std_in.close(),
                                   boost::process::std_out > boost::process::null,
                                   boost::process::std_err > data,
                                   io_context);

io_context.run();
m_message = data.get();

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

Вариант 2: использование вилочного подхода

Предположим, у меня есть libg++, который я могу связать прямо с сервером. Я мог бы использовать «классический» подход вилки для каждого соединения, как показано здесь: https://www.boost.org/doc/libs/1_52_0/doc/html/boost_asio/example/fork/process_per_connection.cpp и вызовите g++_compile() прямо после вилки. Тем не менее, я могу принять новое соединение, поскольку у меня есть отдельный процесс для каждого соединения.

Вариант № 3: используйте boost :: process :: spawn и считайте результат через общую память, например, используя boost :: interprocess

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

Вариант 4:?

Есть ли какой-нибудь способ сделать это без необходимости использования вспомогательного потока или процесса для каждого соединения? В конце концов, в какой-то момент мне нужно дождаться результата в моем основном потоке, блокирующем прием новых подключений. Я немного заполучил сопрограмму, но все еще не понимаю ее деталей и не думаю, что она мне поможет в этом сценарии.

Большое спасибо!

09.05.2019

Ответы:


1

Вы пробовали это решение?

1) Создайте asio :: thread_group и передайте функцию потока F, которая вызывает io_service::run.

asio::thread_group tg{ [](){io_service.run()}, x }; \\ x is the number of threads you want

2) В вашем handle_accept, а не в std::thread t(std::bind(&tcp_connection::run, m_curr_connection)); и t.detach()

do post(io_service, your_work) where your_work can be any callback or functionObject.

Свободно стоящий asio::post поместит your_work в io_service::queue (briefly), откуда он может выполняться одновременно.

На заметку: если your_work может блокировать выполнение, вы можете в дальнейшем превратить их в асинхронные, а не в одновременную блокировку (поскольку другие ожидающие обработчики могут голодать)

25.06.2019
  • Спасибо за ваш ответ и извините за поздний ответ. Тем временем я сам переупорядочивал некоторые вещи и был занят самим проектом. Возможно, я когда-нибудь попробую это сделать, но не сейчас. Буду держать вас в курсе на всякий случай и в любом случае получил за вас голосование;) 03.07.2019

  • 2

    Я также реализовал №2 перед упомянутым примером, и, похоже, он работает так, как задумано для моих нужд. Тем не менее, я был бы очень заинтересован в некоторых материалах, особенно учитывая № 4.

    13.05.2019
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..