Проект создавался для решения одной единственной проблемы. Как быстро и удобно собраться группе людей с самыми разными личными графиками?
Суть в том, что люди вносят свой график создают группы и графики участников сравниваются и все пользователи видят промежутки времени когда они все одновременно свободны. Не нужно вызванивать, дергать друг друга, назначать организатора. Теперь достаточно просто внести свой график добавить друга и посмотреть когда можете пойти вместе.
Также удобно и в коммерческом направлении использовать. Условно есть компания с 20 сотрудниками. Они создают группу и компании будет удобнее подобрать оптимальное время для своего персонала.
Большинство персональных планировщик проигрывают нам, потому что они персональные. Суть в том, что другие продукты делает упор на удобное редактирования своих мероприятий, но дальше профита в 15 минут от этого редактирования выиграть в большинстве случаев нельзя. А выяснить когда группу разных пользователей можно собрать реальная проблема, которой все сталкивались.
MeetUp также планируется сделать систему рекомендаций по графику для пользователя. В самом первом приближении - пользователь ставит метку о том что данное мероприятие нестрого закреплено за этим временем и его можно сместить на определенные границы по времени и дате. Зачем это нужно? Условно 5 человек из 6 имеют свободное время в 1 промежуток, но 6 участник занят в это время. Но занятой участник разрешил переместить мероприятие, которое перекрывает время для всех остальных и алгоритм свободно перемещает данное мероприятие, с уведомлением. Теперь же все 6 из 6 участников могут встретится. Все прекрасно.
MeetUp - это про время. А хотелось бы еще и про расстояние. Как мы видим процесс - MeetUp рекомендует время. DistanceUp (назовем новый сервис так) будет смотреть расположение участников и будет рекомендовать место встречи для этого самого времени, которое будет оптимальное для участников по нескольким критериям. В первую очередь по расстоянию. Дальше же можно будет добавить фильтры по интересам. Удачное время место для рекламы. Создать партерку с несколькими заведениями и рекомендовать их вперед, других.
- MeetUp решает проблему планирования времени, а именно позволяет быстро найти окно для встречи с кем хотите.
- Конкурентов для широкого круга пользователей нет.
- Целевая аудитория - 18 - 55 лет. Люди с загруженным графиком. Чаще всего доход средний и выше. По географии нет ограничений - любой крупный город. Если говорить более узконаправленно то это работающие люди с плотным графиком, чаще всего с большим списком активностей. Которые имеют таких же занятых знакомых.
- Технические требования:
- Cинхронизация с существующими календарями.
- Удобная визуализация встреч в группе.
- Максимально быстрый процесс поиска друга и создание группы. Главные фичи сконцентрированы на сравнении функционала календарей, а не групп и друзей.
- Простой чат + концентрация на опросах для быстрого выбора из пула вариантов.
- nlohmann/json (для Manjaro: sudo pacman -S nlohmann-json)
- gtest (для Manjaro: sudo pacman -S gtest)
- libpqxx (для Manjaro: sudo pacman -S libpqxx)
- boost (для Manjaro: sudo pacman -S boost)
Сервер должен уметь принимать одновременно множество клиентов и обрабатывать запрос каждого в неблокирующем режиме. В данном блоке используется паттерн Команда для возможности задавать различное поведение через единый интерфейс. Для этого реализованы следующие сущности:
- сервер - event loop, ждущий активности на слушающем сокете
- менеджер соединений, хранящий в себе пулл всех имеющихся соединений
- соединение, хранящее тело запроса, требуемый обработчик, буффер для асихронного считывания и ответ
- обработчик - посредник для вызова обрабатывающих функций(в данном случае - парсеров)
- парсер http-запроса, позволяющий обрабатывать запрос по мере его считывания из буффера в асинхронном режиме
- запрос/ответ - структуры, имеющие поля для записи необходымых данных(заголовков, тела и статуса готовности)
- Сервер реализован на базе интерфейса, включающий методы по запуску, принятию клиентского соединения и останова.
- Для инициализации работы необходим адрес, порт и путь к рабочей директории. Добавляются сигналы, посредством которых возможно удаленное управление сервером. После этого открывается слушающий сокет. Сервер готов принимать запросы.
- За активность при подключении клиента отвечает сущность asio::io_context и asio::ip::tcp::acceptor. После подключения клиента создается std::make_shared<ClientConnection>. Это соединение, вместе с клиентским сокетом через move-семантику передается Менеджеру соединений. Последний, в свою очередь добавляет соединение в std::set<connection_ptr> connections_ и вызывает у соединения метод start(). Теперь обработка этого соединения ведется в асихронном режиме по мере считывания данных из буффера.
- Метод start() запускает операцию по асинхронному чтению. ... auto self(shared_from_this()). Когда на сокете удается считать опредленное кол-во байт в буффер, через socket_.async_read_some вызывается функция-обработчик этого события, в которую передается кол-во считанных байтов. Далее вызывется парсер, который находится внутри сущности соединения, реализованный по принципу конечного автомата, который обрабатывает каждый символ через свой метод Consume(char input), и возвращает статус готовности (good, bad, indeterminate). В случае, когда парсинг не завершён, ожидание готовности на чтение вызывается повторно.
- Когда парсинг запроса завершён, вызывается метод обработки запроса request_handler_.handle_request(request_, reply_). На этом этапе запрос передаётся на другой блок бэкенда, который осуществляет бизнес-логику и возвращает ответ. Однако,для отладки сервера далее реализуется логика по отдаче файлов из рабочей директории, через путь в url, для чего существует служебный метод url_decode(), преобразующий url в путь до файла. Данные из файла считываются и передаются в тело ответа. Здесь же заполняются заголовки ответа.
- Если формирование ответа прошло корректно, то ответ готов к отправе. У соединения вызывается метод do_write(), асинхронно пишущий в клиентский сокет ответ. Boost::asio позволяет отправлять данные в виде пакетов, полученных разбиением ответа на множество мелких буфферов через метод reply_.to_buffers(), минимизируя опасность медленных клиентов. Если запись всех данных прошла успешно, то в пулле клиентских соединений вызывается метод о закрытии соединения.
Термины и определения
- Паттерн Команда - поведенческий шаблон, в котором объект используется для инкапсуляции всей информации, необходимой для выполнения действия или вызова события в более позднее время. Эта информация включает в себя имя метода, объект, который является владельцем метода и значения параметров метода.
Четыре термина всегда связаны с шаблоном Команда: команды (command), приёмник команд (receiver), вызывающий команды (invoker) и клиент (client). Объект Command знает о приёмнике и вызывает метод приемника. Значения параметров приёмника сохраняются в команде. Вызывающий объект (invoker) знает, как выполнить команду и, возможно, делает учёт и запись выполненных команд. Вызывающий объект (invoker) ничего не знает о конкретной команде, он знает только об интерфейсе. Оба объекта (вызывающий объект и несколько объектов команд) принадлежат объекту клиента (client). Клиент решает, какие команды выполнить и когда. Чтобы выполнить команду он передает объект команды вызывающему объекту (invoker). - std::make_shared - более безопасный способ создать shared_ptr. Поскольку "," не является Sequence point и возможна утечка ресурсов. К тому же позволяет минимизировать использование new
- std::shared_from_this() метод, базового класса(std::enable_shared_from_this<my_class>), создающий shared_prt текущего объекта
- std::enable_shared_from_this - позволяет внутри объекта создавать shared_prt на себя же. Необходимо наследоваться от этого класса, чтобы иметь возможность вызывать shared_from_this()
- [this, self]Здесь auto self(shared_from_this()). Такой захват в лямбду позволяет использовать методы класса ClientConnection, а с помощью self, создавая shared pointer на себя же, мы пролонгируем время жизни объекта, когда потребуется вызвать do_read() и выйти из области видимости
- boost::asio::io_context - класс, через который осущетсвляется синхронизация всех многопоточных и асинхронных операций. Метод run() запускает аналог event loop, содержащий очередь задач, которые добавляются после вызово асинхронных операций. run() будет работать, пока ожидающие операции завершаются или пока вы сами не вызовете io_service::stop(). Чтобы сохранить экземпляр io_service работающим добавляется одна или несколько асинхронных операций
- boost::asio::resolver.resolve(address, port)
- boost::asio::acceptor - сущность в Boost.Asio, позволяющая обрабатывать входящие соединения
- boost::asio::async_accept() - асинхронно вызывает обработчик с сигнатурой void (boost::system::error_code ec, boost::asio::ip::tcp::socket socket) при подключении
- boost::asio::ip::tcp::endpoint. Конечная точка - это адрес подключения вместе с портом.
- boost::asio::socket boost::asio::socke.shutdown() - эта функция отключает операцию send , receive или обе сразу же после вызова
- boost::asio::async_connect(endpoint) - эта функция асинхронно подключается по данному адресу.
- boost::asio::buffer(some_buffer). В основном оборачивается любой буфер, который у нас есть в классе, что позволяет функциям из Boost.Asio итерироваться по буферу. Экземпляр some_buffer должен удовлетворять некоторым требованиям, а именно ConstBufferSequence или MutableBufferSequence. Boost.Asio уже содержит некоторые классы, моделирующие эти требования. Вы не обращаетесь к ним напрямую, вы используете функцию buffer(). Так можно оборачивать char[], std::string, std::vector и др.
- boost::asio::signal::set. Множество зарегистрированных сигналов. Когда приходит один из таких сигналов вызывается обработчик с сигнатурой void (boost::system::error_code ec, int signo)
- boost::asio::signals_.async_wait - ожидает поступление зарегистрированного сигнала и вызывает обработчик
- boost::asio::async_read_some(buffer, handler). Эта функция запускает асинхронную операцию получения данных от сокета. Для асинхронных функций обработчик имеем следующую сигнатуру void handler(const boost::system::error_code& e, size_t bytes).
- boost::asio::async_write(stream, buffer [, completion] ,handler). Эта функция асинхронно пишет в поток. По завершении вызывается обработчик. Он имеет следующую сигнатуру void handler(const boost::system::error_code & err, size_t bytes);. При необходимости вы сами можете задать completion функцию
- boost::asio::async_read_some(stream, buffer, handler) - вызывает обработчик, когда на сокете имееются данные на чтение. При этом не обязательно, чтобы сообщение было доставлено целиком. Удобно, когда данные из сообщения могут обрабатываться через конечные состояния
- std::tie служит для распаковки переменных из возвращаемого кортежа. Например std::tie(a, b) = std::make_tuple(2, 3);
- std::ignore предназначен для использования с std::tie при распаковке std::tuple в качестве заполнителя для аргументов, которые не используются.
- std::hex используется для предстваления чисел в шестнадцатеричной форме. Также позволяет преобразовывать числа из stream. std::istringstream("2A") >> std::hex >> n;
- Организатор - RouteImpl, принимает запрос и данные, отдает ответ. Имеет пул потоков-воркеров
- Серия парсеров - Parser*
- Серия методов коннекторов к БД - DB*
- Оберка для подключения к БД - DBConnection
- Функтор-организатор для работы с БД - DBManager
- Серия обработчиков запросов - Handler*
- Структуры - копии полей с БД - Group, Event и прочие
- Объект-траспортер данных - Context
- Занесение запроса в мапу для ожидения ответа.
- Занесение запроса в очередь на исполнение.
- Любой воркер забирает задачу из очереди.
- Достает тип запроса.
- Находит соотвествующий парсер и обработчик.
- Парсит в структуру.
- Обработает.
- Заносит в строчку.
- Ставит в соотвествие ответ и запрос в начальную мапу.