Вопрос по Boost-asio архитектуре
От: Yaroslav  
Дата: 08.01.18 13:06
Оценка:
Здравствуйте.

Если читать советы по boost то пропагандируется идеология:
1 io_service и N потоков к нему на все классы в программе. Чтобы просто увеличив количество потоков улучшить производительность программы.

Звучит хорошо и красиво, примеры на boost.org вовсю доступно и понятно описывают реализацию для TCP-сервера и TCP-соединения. Но не более того!! Всё что выше по иерархии (т.е. просто архитектуру такой программы) почему-то "забыли упомянуть", или видимо посчитали что "дальше очевидно", хотя на мой взгляд там начинается самое интересное!!!.

Итак приведу странный пример, который меня очень беспокоит (исходники если надо выложу потом):.

1. Класс TcpConnection (унаследован от enable_shared_from_this<TcpConnection>). Получает movable boost::asio::ip::tcp::socket.
Имеет 2 сигнала

// сигнал прочитанных данных
boost::signals2::signal<void(shpTcpConnectionS, const DataPortion&)> m_OnDataReaded;

// сигнал дисконнекта
boost::signals2::signal<void(shpTcpConnectionS)> m_OnDisconnected;

2. Класс ProtocolParser — этакий парсер некоего юзерского протокола. Принимает io_service& в конструкторе и shared_ptr<TcpConnection>. Предполагается что получая данные от shared_ptr<TcpConnection> выполняется парсинг каких-то данных, возможно отправка каких-то данных в соединение. По факту приёма каких-то особых данных может исторгнуть из себя посредством boost::signals2::signal запросы на какие-то действия.

3. TCP-сервер аля из примеров на boost.org (получает в конструкторе io_service&). Исторгает из себя новое соединение через boost::signals2::signal<void(std::shared_ptr<TcpConnection>)> m_OnNewConnection;

4. Класс Controller, который по сути "владелец всего".
Содержание:
1. Один io_service
2. N потоков на с запущенным io_service::run() для io_service из п.1
3. TCP-сервер
4. map<id, shared_ptr<ProtocolParser> >
5. strand

Связи и поведение всей программы:

Сигнал TCP-сервера m_OnNewConnection завязан на обработчик в Controller. Диспетчеризация через strand. На каждое новое соединение создаётся свой shared_ptr<ProtocolParser>, ему передаётся в работу shared_ptr<TcpConnection>. Новый shared_ptr<ProtocolParser> укладывается в map.

Сигналы от каждого ProtocolParser подключены на обработчики Controller. Т.е. фактически выполняется управление контроллером через TCP-соединение.

Примерно так.

Всё это более менее прекрасно работает до тех пор пока вдруг Controller'у вдруг не приспичит "убить" какой-то ProtocolParser (причём ProtocolParser должен находиться в состоянии активного обмена по TCP).

Совершенно нет проблем это сделать если каждый объект класса (Controller, ProtocolParser, TcpConnection, TcpServer) владеет своей парой io_service+поток.

Интересно как это сделать при методологии 1 io_service и N потоков на всю программу?

Ведь при попытке удалить ProtocolParser нужно отменить все операции на связанном с ним shared_ptr<TcpConnection> и удалить на него ссылку. Но это не решает проблему, т.к. один из потоков в данный момент обслуживающий TcpConnection может уже передавать данные в ProtocolParser, который находится в состоянии уничтожения/уничтожился.

Один из вариантов что я вижу — цеплять к сигналу TcpConnection::m_OnDataReaded обработчик не голый указатель на ProtocolParser а также shared_ptr<ProtocolParser> (и ProtocolParser унаследован от enable_shared_from_this). Но это всё выглядит очень печально, т.к. данный пример можно развивать дальше. ProtocolParser может быть связан с неким классом A (со своей логикой), который в свою очередь связан с классом B,... Городить на каждый signal-connect shared_ptr выглядит очень странно.

Как делается правильно такая задача?
Отредактировано 08.01.2018 13:11 Yaroslav . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.