Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

95 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Сборка и установка

Описание сборочной архитектуры

В данном проекте сборка осуществляется с помощью CMake, а самих файлов CMakeLists.txt несколько. В корневом файле заданы глобальные настройки и подключены вложенные директории tests и src.

  • В src/ скрипт описывает сборку библиотеки в двух вариантах (статическая и динамическая) и многопоточных приложений, которые в свою очередь подключают и используют эти библиотеки. Вся компиляция проводится из исходных файлов
  • В tests скрипт описывает сборку юнит-тестов с помощью библиотеки GTest.

Описание процесса компиляции

  1. Для начала, необходимо склонировать исходный код в систему
git clone git@github.com:exodess/infotecs_cpp_developer.git
  1. Перейти в новую директорию
cd infotecs_cpp_developer
  1. После этого нужно собрать проект (вместо build можно указать другое название директори, куда вы хотите сохранить сборку)
cmake -S . build 
  1. Затем перейти в директорию сборки
cd build
  1. Выполнить установку
make
  1. Готово. Теперь можете запускать и тестировать работоспособность проекта
./tests/TestLogger # запуск тестов
./src/CheckLibApp <file> <default_level> # запуск приложения из части 2 
./src/CollectStatApp <connection_parameters> N T # запуск приложения из части 3

Дополнительно

Генерация документации

make docs # запуск в папке build

Документация будет формироваться в корне проекта, в папке. Для просмотра необходимо открыть файл docs/html/index.html в браузере

Также при запуске тестов необходимо завершить работу приложений CheckLibApp и CollectStatApp, чтобы они проходили без ошибок img_2.png

Часть 1

В соответствие с ТЗ была разработана библиотека для записи текстовых сообщений в журнал. В качестве журнала используются файл и сокет, интерфейсы логгирования не отличаются.
library_uml_diagram.png

Класс Logger является основным классом библиотеки. Для поддержки разного типа логгирования (в файл и сокет) был реализован абстрактный класс BWriter, который представляет интерфейс записи сообщений в журнал. Классы, наследуемые от этого интерфейса, реализуют конкретный способ записи (в файл или в сокет). Это позволяет гибко управлять и, по возможности, дополнять библиотеку, ведь непосредственная IO логика вынесена в отдельные классы, которые придерживаются описанного интерфейса.

При создании логгера в конструктор принимается в качестве аргумента имя журнала и минимальный уровень важности сообщения. Внутри конструктора происходит анализ имени журнала и выбор того, какой тип он представляет - файл или сокет.
Если журнал - это сокет, то "записыватель" _writer создается как объект класса SocketWriter. Параметры подключения указываются в виде <IP-адрес>:<Port>, например, 127.0.0.1:8080
Если журнал не подходит под сокет, то он создается как объект класса FileWriter.

Внутри класса Logger, кроме _writer поля, через которое происходит запись текущего сообщения в логгере, также хранится static словарь всех объектов BWriter, ассоциированных с именем журнала. Это сделано для того, чтобы при повторном открытии того же журнала в другом логгере не создавать объект BWriter, а использовать уже ранее созданный. За счет хранения в виде умных указателель обеспечивается free-leaked.

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

Часть 2

В части 2 было реализовано консольное многопоточное приложение CheckLibApp для проверки библиотеки записи сообщений в журнал из первой части.

Запуск приложения происходит по следующему шаблону:
./src/CheckLibApp <file_or_socket_setting> <minimum_level>

  • <file_or_socket_setting>: Указывает журнал для сохранения логов. Важно, что приложение может записывать как в файл (например, file.log), так и в сокет (например, 127.0.0.1:8080), но без активного приложения из части 3 он работать не будет.
  • <minimum_level>: Уровень важности сообщения по умолчанию. При создании логгера это значение используется как минимальный уровень важности, которое должно иметь сообщение, чтобы оно было записано в журнал

Логика работы приложения

Сначала приложение принимает имя журнала и уровень важности по умолчанию. Если один или оба из этих параметров не будет получен, то программа завершит свою работу.

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

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

Если пользователь захочет записать сообщение, у котого уровень важности ниже минимального, то программа предложит заменить значение уровня на новое. Если ответ утвердительный, то программа запросит новое значение, если отрицательное, то продолжит работу дальше. Если пользователь при утвердительном ответе введет корректное значение (от 0 до 2), то это значение уровня важности будет использоваться по умолчанию. Иначе будет использовано старое значение.

Программа также способна обрабатывать некорректный ввод: когда пользатель вводит уровень важности и указывает значение, не входящее в промежуток от 0 до 2, то во всех случаях (кроме смены уровня по умолчанию) будет использовано самое близкое значение - если указано отрицательное значение, то 0, если положительное и больше 2, то 2.

Архитектура приложения

Структура классов:
check_lib_app_uml_diagram.png

Главный класс приложения - CheckLibApp. Внутри него находится логгер, через который происходит запись логов в журнал. Запись логов осуществляется асинхронно, передача данных потокобезопасна за счет того, что в BWriter-наследуемых классах запись в методе Write() синхронизируется через мьютекс.

Приложение корректно обрабатывает исключения, которые возникают в библиотеке логгирования. Но для реализации бизнес-логики исключения не используются: все методы объявлены noexcept

Примеры работы приложения:

При указании параметров подключения сокета без запущенного приложения из части 3, будет выведена ошибка, и программа завершится img_4.png

Если при запуске приложения не было указано никаких параметров, то программа их запрашивает у пользователя
img.png

Если и в этот раз пользователь не укажет параметры, то программа завершит работу.
img_1.png

Если количество аргументов будет больше, чем нужно, то программа сразу же завершает программу img_2.png

Важно! При смене уровня важности эта настройка применяется только к следующему сообщению img_3.png

Часть 3

В части 3 было реализовано консольное многопоточное приложение для сбора статистик по данным из сокета (от библиотеки логгирования из части 1)

Запуск приложения происходит по следующему шаблону:
./src/CollectStatApp <connection_parameters> N T

  • <connection_parameters>: Параметры подключения сокета в виде <IP-адрес>:<Порт>
  • N: количество сообщений, через которые будет выводиться статистика
  • T: таймаут в секундах, по истечению которого будет выводиться статистика, при условии, что она изменилась с последнего вывода

Параметры N и T должны быть больше 0

Логика работы приложения

Сначала приложение принимает параметры подключениия и значения N и T. Если какое-то значение не будет указано через аргументы командной строки, то программа запросит их напрямую у пользователя. Если что-то не все равно не будет получено, то программа завершит свою работу.

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

При достижении N-ого сообщения будет выведена статистика, которая описана в ТЗ. Также через каждые T секунд также будет выводиться статистика, при условии, что с последнего вывода она была обновлена (т.е с последнего вывода пришло новое сообщение).

Архитектура приложения

Структура классов:
collect_stat_app_uml_diagram.png

Класс Server реализует создание серверной части с указанными параметрами подключения, а также прослушивание сети на предмет подключения к нему клиентов. Сетевое взаимодействие построено на сокетах.

Главный класс приложения - CollectStatApp. Вся логика сетевого взаимодействия вынесена в класс Server, что позволяет внутри не осложнять код класса приложения, а пользоваться дополнительным слоем абстракции в виде класса сервера.

Внутри приложения также реализован функция таймера. В ней через каждые T секунд вызывается метод для вывода статистики. Сам таймер при инициализации программы помещается в отдельный поток, чтобы не блокировать работу основного потока.

В методе exec(), который играет роль главного цикла приложения, происходит прослушивание соединения средствами класса Server. Когда соединение установлено, то программа начинает считывать данные, которые посылает клиент.

Для каждого клиентского узла создается отдельный поток, в котором происходит чтение приходящих данных, обновление статистики и вывод ее в консоль, если принятое сообщение - N-ое по списку. Все потоки, созданные для чтения данных, хранятся в одном списке, чтобы потом в деструкторе высвободить ресурсы через join().

Для обеспечения корректной работы внутри многопоточной программы используются две atomic переменные типа bool. Одна из них сигнализирует всем созданным потокам о завершении работы, когда произойдет ошибка, а вторая показывает, обновлена ли статистика или нет.

Пример работы приложения

Если при запуске не указаны параметры, то программа запросит их у пользователя
img.png

Работа приложения (слева запущено CheckLibApp, справа - CollectStatApp):
collect_stat_app_work.gif

Еще одно изображение
img_1.png

Приложение также сигнализирует о подключении и отключении клиента img.png

Приложение корректно работает с двумя и более клиентами img.png

About

Тестовое задание на стажера по направлению C++ в компанию Infotecs

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages