Остановка однопоточного C-сервера с помощью epoll

Я пишу TCP-сервер на C в качестве упражнения для задания, которое, как я знаю, я получу в следующем году. Я реализовал ядро, которое использует один поток для обработки всех подключений, для этого он использует средство epoll. Ядро предоставляет остальной части программы API, очень похожий на предложения C# для асинхронной сети; все это отлично работает, и после некоторой странности epoll - мне внезапно пришлось использовать дублирование файловых дескрипторов, чтобы поддерживать его работоспособность, но, к счастью, я понял, почему - мне удалось избавиться от ошибок и утечек. Однако я не уверен, как остановить сервер. В настоящее время я написал подпрограммы очистки, которые очищают основные структуры данных, но они никогда не вызываются. Основной цикл epoll_wait никогда не заканчивается. Как остановить этот цикл?

Я подумал о следующем:

  • Я использую глобальный флаг, защищенный мьютексом, который проверяется основным потоком каждый раз перед вызовом epoll_wait(). Когда флаг становится истинным, основная нить прерывает свой цикл и очищает все. Это работает, когда я использую основной поток для установки флага, например, когда он выходит из основного контекста через обратный вызов (например, обратный вызов, который вызывается при установлении нового соединения с прослушивающим сокетом; ядро ​​ничего не знает о реализация этих обратных вызовов, и в этих обратных вызовах я мог бы установить стоп-флаг. В этом случае мьютекс даже не был бы необходим.) Однако это не всегда работает, когда мне нужен внешний поток (поток диспетчеризации событий графического интерфейса, возможно, • Если я хочу написать графический интерфейс для мониторинга сервера во время его работы, это может пригодиться) для установки этого флага. Конечно, основной поток остановит сервер, как только произойдет повторная проверка флага, но проблема в том, что он не будет часто проверять этот флаг, если сервер простаивает (например, когда нет клиентов и нет новых подключений). Тогда epoll_wait() просто останется заблокированным, и мне придется убить сервер -- с помощью Ctrl-C или чего-то подобного. Это не то, чего я хочу.

  • Чтобы решить эту проблему, я подумал о создании канала и добавлении этого канала fd в контекст epoll сервера при его запуске, чтобы он реагировал и возвращался из epoll_wait() всякий раз, когда какой-либо поток записывает некоторые (нерелевантные) данные в этот канал. Это может позволить какому-то потоку заставить ядро ​​проверить свой флаг и выйти. Но это похоже на грязное решение. Есть ли лучшая альтернатива? Если нет, будет ли работать это решение для каналов?

Заранее спасибо, как обычно, любые замечания (если конструктивные) приветствуются!


person DiscobarMolokai    schedule 24.06.2014    source источник


Ответы (1)


Вы останавливаете цикл, используя одну из обычных языковых конструкций, такую ​​как break или условие в цикле while или for.

Но я подозреваю, что вы хотели спросить, когда остановить цикл. Вы, вероятно, захотите сделать это, когда ваша программа получает сигнал, а именно SIGTERM (например, когда диспетчер служб, такой как systemd, хочет, чтобы ваш демон был остановлен) или SIGINT (например, когда вы нажимаете CTRL+C). Если вы ничего не делали с сигналами в программе, ваша программа немедленно остановится, как только будет получен любой из этих двух сигналов, и у вас не будет возможности очиститься.

Безусловно, самый простой способ поймать сигналы в Linux — использовать средство signalfd (см. справочную страницу). Таким образом, вам не нужно иметь дело с обработчиками сигналов и их многочисленными проблемами, такими как условия гонки, тупиковые ситуации и случайные системные вызовы, сбой из-за сигнала. Вместо этого вы получаете файловый дескриптор, который регистрируете в своем экземпляре epoll для уведомлений о возможности чтения. Когда сигнал готов, дескриптор файла будет доступен для чтения, и вы сможете прочитать информацию о сигнале в структуру. Здесь у вас есть возможность вырваться из основного цикла и заняться очисткой.

Но имейте в виду, что есть очень хорошие библиотеки (например, libuv), которые абстрагируются от всех этих деталей и поддерживают несколько платформ.

ОБНОВЛЕНИЕ

Если вы хотите остановить сервер из другого потока, то у вас есть экземпляр более общей проблемы: передача информации в цикл обработки событий. Хотя ваша идея послать сигнал самому себе будет работать, это своего рода хак. Есть как минимум два «правильных» решения: pipe и eventfd, причем последнее специфично для Linux и специально разработано для этой проблемы. Для вашего использования их обоих будет достаточно.

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

Если вам нужно передать больше информации из потока в цикл обработки событий (что вам не нужно просто для остановки сервера), вы можете поместить ее куда-нибудь в память, возможно, в очередь, с надлежащей блокировкой.

person Ambroz Bizjak    schedule 24.06.2014
comment
+1 Я, конечно, могу это использовать, спасибо. Я уже использую условие цикла while. Моя проблема заключалась в том, что epoll_wait блокирует, и я не мог контролировать эту блокировку с помощью другого потока в том же приложении. Но используя signalfd, я могу заставить поток, который хочет убить сервер, поднять сигнал. Это решает мою проблему, спасибо. - person DiscobarMolokai; 24.06.2014