-
Notifications
You must be signed in to change notification settings - Fork 228
ДЗ Блокирующий ввод вывод
Изучить построение сетевого приложения на основе блокирующего API ввода-вывода, рассмотреть достоинства и недостатки этого метода.
Основной скелет для домашнего задания уже написан в виду:
-
src/protocol/Parser.{h,cpp}
- парсер протокола memcached (тесты и пойманные блохи категорически приветствуются) -
src/network/mt_blocking/ServerImpl.cpp
- прототип сервера на блокирующем API
Если сейчас запустить afina --network mt_block --storage mt_lru
, то сразу после подключения сервер выведет клиенту сообщение-заглушку и закроет соединение. Задача в том, чтобы реализовать общение по memcached
протоколу.
Парсер задекларирован в src/protocol/Parser.h
и предоставляет следующее API:
-
bool Parse(const char *input, const size_t size, size_t &parsed)
- принимает строку символов и продолжает разбор данных так, как будто бы это продолжение строки из предыдущего вызова. В параметр parsed возвращается количество байт из строки, которое было разобрано, больше их в парсер передавать не надо. Метод возвращаетtrue
как только команда была считана из входного потока. -
std::unique_ptr<Execute::Command> Build(uint32_t &body_size) const
- Если метод Parse вернулtrue
, данный метод создает экземпляр команды, которую можно выполнить надStorage
с помощью методаExecute
. Возвращаемый параметрbody_size
показывает сколько еще надо прочитать байт из сокета, чтобы получить аргумент команды - Перед чтением следующей команды нужно вызвать метод
Reset
Каждая команда реализует интерфейс из include/afina/execute/Command.h
:
class Command {
public:
Command() {}
virtual ~Command() {}
virtual void Execute(Storage &storage, const std::string &args, std::string &out) = 0;
};
Метод Execute
выполняет какое-то действие над заданным хранилищем данных. args
это аргумент команды переданный по сети, к примеру это значение ключа для add
или set
, может быть пустым, как у get
.
В выходной параметр out
команда должна вернуть ответ, который будет записан клиенту. После отправки ответа, сервер также должен записать в сокет \r\n
src/network/mt_block/ServerImpl.cpp
это прототип сервера на блокирующем API, нужно реализовать следующую логику:
- После создания нового соединения, нужно:
- Если предел параллельных соединений еще не достигнут, создать новый
Worker
поток и обработать соединение в нем - Если предел достигнут, тогда закрыть соединение
- Если предел параллельных соединений еще не достигнут, создать новый
Внутри каждого рабочего потока:
- Прочитать команду
- Прочитать аргумент команды, если
body_size
> 0 - Выполнить команду, если при выполнение возникло исключение, вернуть клиенту строку
SERVER_ERROR <описание>\r\n
- Записать результат команды в соединение с пользователем, затем записать
\r\n
- Повторять, пока либо сервер не остановится, либо соединение не закроется.
При остановке сервера:
- Дождаться окончания выполнения текущей команды
- Записать результат
- Закрыть соединение
- Как только будет закрыт последний рабочий поток, остановить основной поток сервера
- Отпустить все потоки, который ждали остановки сервера в
Join
внимание: пример того, как может быть устроен цикл обработки одного соединения можно посмотреть в src/st_blocking/ServerImpl.cpp
В первом домашнем задание было написано LRU
хранилище. В готовом виде его использовать вместе с mt_block
нельзя, т.к к нему будут обращаться насколько потоков, что создаст race condition.
Исходя из этого нужно реализовать класс src/storage/ThreadSafeSimpleLRU.h
:
- Содержит в себе экземпляр класса
SimpleLRU
- Глобальный
std::mutex
- Каждый вызываемый метод, должен захватить
mutex
, делегировать работу вSimpleLRU
, затем отпустить mutex и вернуть результат.
внимание: т.к все вызываемые методы интерфейса storage виртуальный, то "делегирование" может быть сделано через наследование, как написано в коде afina
, однако вы можете и переписать этот код, сохранив ссылку на SimpleLRU
явно
Тестов нет, проверять руками. Ожидаемое поведение:
[user@domain build] ./src/afina --network mt_block --storage mt_lru
Запустит приложение, к которому можно подсоединиться по порту 8080. Теперь туда можно отправлять команды на исполнение:
[user@domain build] echo -n -e "set foo 0 0 6\r\nfooval\r\n" | nc localhost 8080
Для того, чтобы проверить, что сервер дожидается выполнения команд при остановке, можно в какую-то из команд добавить sleep