В одном из своих проектов я активно использую поллинг при помощи epoll(7). Проект этот - HTTP(s) прокси с довольно развесистой бизнес логикой. В приложении постоянно приходится менять маску событий, а так же одновременно обрабатывать довольно большое количество сетевых соединений.
Когда проект начинался, всю обвязку вокруг epoll мы сделали сами, ну и разумеется вдоволь прошлись по граблям. За пример, при разработке, я брал тоже прокси, написанное как раз с целью понять как обрабатывать большое количество соединений в однопоточном приложении и при этом не утонуть в "code spaghetti". Ссылочка на этот проект: https://github.com/gpjt/rsp
Ссылку на свой проект дать не могу - код проприетарный.
Когда все более-менее влетело, я начал смотреть по сторонам на предмет "а нельзя ли немного упростить код". И разумеется код упростить можно было, используя что-то типа libevent или libev. Обе эти библиотеки предоставляют API для портируемой работы с событиями и избавляют от необходимости самому писать event-loop.
libevent, насколько я понял, по-старше будет, но на данный момент не развивается, поэтому ее я оставил на потом и решил написать небольшой примерчик использую libev.
Итак...
Сервер, при старте, слушает 1025 порт в ожидании клиентских соединений, если в командной строке не был указан другой номер порта.
Начало стандартное - готовим структуру sockaddr_in
Инициализируем структуру типа ev_loop:
Описание флагов можно найти в man 3 libev или по адресу:
http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
Хочу лишь заметить, что libev позволяет указывать тип механизма поллинга при создании цикла событий, а так же позволяет это делать не перекомпилируя приложение, через переменные окружения(LIBEV_FLAGS).
Рекомендуемое, авторами библиотеки знание: EVFLAG_AUTO. В этом случае, при создании цикла обработки событий библиотекой будет выбран наилучший метод поллинга.
Далее инициализируем watcher. Это некий тип данных ev_TYPE, где TYPE это тип событий, поддерживаемый библиотекой. Для обработки ввода/вывода это io и соответственно тип будет ev_io. Функции(а на самом деле макросы) относящиеся к работе с тем или иным типом watcher, так же в своем имени имеют соответствующий префикс типа. К примеру:
ev_io_start() или ev_io_stop().
Watcher можно инициализировать двумя способами:
вызовами ev_init(), а затем ev_TYPE_set(). Либо: ev_TYPE_init().
Я использовал второй способ:
ev_io_init(&sock_watcher, accept_connect, listen_fd, EV_READ);
, где:
sock_watcher - указатель на соответствующую структуру типа ev_io;
accept_connect - указатель на callback; должен быть типа: void(*)(struct ev_loop *loop, ev_TYPE *w, int revents)
listen_fd - файловый дескриптор, на котором ожидаются события;
EV_READ - тип событий, на указанном дескрипторе, которые будут отрабатываться указанным callback.
В моем случае, callback вызываемый при принятии нового соединения это функция accept_connect(). На io, насколько я понял, может быть только два типа событий, это - EV_READ и EV_WRITE. Есть еще один, специальный тип это - EV_ERROR. Этим типом библиотека сигнализирует о внутренних проблемах приложения.
Далее сообщаем, что хотим начать обрабатывать события на указанном watcher:
Дальше, по сути, делается тоже самое - при создании нового подключения(в accept_connect()) создается новый watcher типа io, на его события вешается callback on_read(), в котором обрабатываются события ввода/вывода на обслуживаемом соединении. В созданном watcher есть поле data, в котором можно храниться указатель на пользовательские данные. В моем случае, это указатель на структуру типа buffer_t, которая представляет буфер через который идет обмен данными в данном конкретном соединении. Все операции с сокетами выполняются в неблокируемом режиме. На этом все.
Для себя я не уяснил только один момент - так как библиотека может использовать различные backend, в том числе и epoll, то какой из типов поллинга в этом случае используется - edge-triggered или level-triggered. Явного указания я сходу не нашел.
Ссылка на код: https://github.com/apofiget/echo_srv
Когда проект начинался, всю обвязку вокруг epoll мы сделали сами, ну и разумеется вдоволь прошлись по граблям. За пример, при разработке, я брал тоже прокси, написанное как раз с целью понять как обрабатывать большое количество соединений в однопоточном приложении и при этом не утонуть в "code spaghetti". Ссылочка на этот проект: https://github.com/gpjt/rsp
Ссылку на свой проект дать не могу - код проприетарный.
Когда все более-менее влетело, я начал смотреть по сторонам на предмет "а нельзя ли немного упростить код". И разумеется код упростить можно было, используя что-то типа libevent или libev. Обе эти библиотеки предоставляют API для портируемой работы с событиями и избавляют от необходимости самому писать event-loop.
libevent, насколько я понял, по-старше будет, но на данный момент не развивается, поэтому ее я оставил на потом и решил написать небольшой примерчик использую libev.
Итак...
Пишем ECHO-сервер на libev.
Сервер, при старте, слушает 1025 порт в ожидании клиентских соединений, если в командной строке не был указан другой номер порта.
Начало стандартное - готовим структуру sockaddr_in
sock.sin_port = htons(port);и создаем прослушиваемый неблокируемый сокет:
sock.sin_addr.s_addr = htonl(INADDR_ANY);
sock.sin_family = AF_INET;
listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
bind(listen_fd, (const struct sockaddr *)listen_addr, sizeof(struct sockaddr_in));Если все прошло успешное, создаем цикл событий в котором будем обрабатывать прием новых соединений, а так же прием данных от уже установленных соединений.
listen(listen_fd, BACKLOG_SIZE);
Инициализируем структуру типа ev_loop:
loop = ev_loop_new(EVFLAG_NOENV | EVBACKEND_EPOLL);
Описание флагов можно найти в man 3 libev или по адресу:
http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
Хочу лишь заметить, что libev позволяет указывать тип механизма поллинга при создании цикла событий, а так же позволяет это делать не перекомпилируя приложение, через переменные окружения(LIBEV_FLAGS).
Рекомендуемое, авторами библиотеки знание: EVFLAG_AUTO. В этом случае, при создании цикла обработки событий библиотекой будет выбран наилучший метод поллинга.
Далее инициализируем watcher. Это некий тип данных ev_TYPE, где TYPE это тип событий, поддерживаемый библиотекой. Для обработки ввода/вывода это io и соответственно тип будет ev_io. Функции(а на самом деле макросы) относящиеся к работе с тем или иным типом watcher, так же в своем имени имеют соответствующий префикс типа. К примеру:
ev_io_start() или ev_io_stop().
Watcher можно инициализировать двумя способами:
вызовами ev_init(), а затем ev_TYPE_set(). Либо: ev_TYPE_init().
Я использовал второй способ:
ev_io_init(&sock_watcher, accept_connect, listen_fd, EV_READ);
, где:
sock_watcher - указатель на соответствующую структуру типа ev_io;
accept_connect - указатель на callback; должен быть типа: void(*)(struct ev_loop *loop, ev_TYPE *w, int revents)
listen_fd - файловый дескриптор, на котором ожидаются события;
EV_READ - тип событий, на указанном дескрипторе, которые будут отрабатываться указанным callback.
В моем случае, callback вызываемый при принятии нового соединения это функция accept_connect(). На io, насколько я понял, может быть только два типа событий, это - EV_READ и EV_WRITE. Есть еще один, специальный тип это - EV_ERROR. Этим типом библиотека сигнализирует о внутренних проблемах приложения.
Далее сообщаем, что хотим начать обрабатывать события на указанном watcher:
ev_io_start(loop, &sock_watcher);и запускаем цикл обработки событий:
ev_run(loop, 0);В первом приближении это все.
Дальше, по сути, делается тоже самое - при создании нового подключения(в accept_connect()) создается новый watcher типа io, на его события вешается callback on_read(), в котором обрабатываются события ввода/вывода на обслуживаемом соединении. В созданном watcher есть поле data, в котором можно храниться указатель на пользовательские данные. В моем случае, это указатель на структуру типа buffer_t, которая представляет буфер через который идет обмен данными в данном конкретном соединении. Все операции с сокетами выполняются в неблокируемом режиме. На этом все.
Для себя я не уяснил только один момент - так как библиотека может использовать различные backend, в том числе и epoll, то какой из типов поллинга в этом случае используется - edge-triggered или level-triggered. Явного указания я сходу не нашел.
Ссылка на код: https://github.com/apofiget/echo_srv
Комментариев нет:
Отправить комментарий