Тайм-аут простаивающих соединений на сервере epoll
Я пишу TCP-сервер в C, который использует epoll() мультиплексирование ввода / вывода для управления одновременными соединениями. Я хочу установить тайм-аут соединений, которые простаивают более разрешенного времени.
До сих пор я храню переменную last_active time_t, связанную с каждым соединением, которое я обновляю до текущего времени в обработчике событий. Перед тем, как это сделать, я проверяю, прошло ли больше времени, чем было разрешено, с момента последнего события, и если да, я разрываю соединение.
Пока все хорошо, но это не совсем то, что я хочу, потому что тайм-аут срабатывает только при первом событии вне времени, но если соединение остается неактивным, мой код не обнаруживает его до тех пор, пока оно снова не станет "активным".
Я видел, как это делается на серверах на основе select(), путем линейного обхода набора интересов во время каждой итерации цикла событий и очистки там неактивных соединений. Это не проблема в select, потому что вы все равно уже должны выполнить этот обход, но я использую epoll() именно для того, чтобы избежать этого. Если я сделаю это, epoll не лучше, чем select.
Я также изучил параметры сокетов, самым близким из которых я нашел SO_RCVTIMEO, который заставляет read()/recv() возвращать ошибку, если он ждал больше указанного времени. Но так как я работаю с мультиплексированием ввода / вывода и сокеты находятся в неблокирующем режиме, это не имеет смысла, потому что сокеты не блокируются.
Буду признателен за понимание того, как решить эту проблему. Большое спасибо.
1 ответ
Так как вы знаете время last_active для каждого сокета, вы можете вычислить время, в течение которого следующий сокет должен быть отключен (при условии, что в промежуточный период больше не происходит ввода-вывода), и передать аргумент timeout для epoll_wait()
чтобы он проснулся в это время, чтобы вы могли выполнить закрытие соединения.
Это оставляет другую часть проблемы - вы хотите иметь возможность выполнять эти вычисления без итерации по всем сокетам на каждой итерации цикла обработки событий.
Вы можете сделать это, поддерживая структуру данных (например, очередь с приоритетами), которая поддерживает эффективный поиск элементов с самым низким приоритетом (например, O(1) или O(log(N)). В этом случае вы можете использовать значение last_active сокета в качестве значения приоритета. Затем перед каждой итерацией цикла обработки событий вы просматриваете структуру данных, чтобы выяснить, какой сокет имеет самый низкий приоритет (иначе говоря, какой сокет будет следующим по времени и должен быть отключите, если на нем больше не будет трафика), и используйте его для настройки epoll_wait()
тайм-аут.
Обратите внимание, что для поддержания структуры данных вам нужно будет обновлять ее каждый раз, когда сокет отправляет или получает данные (чтобы отрегулировать его приоритет, чтобы отразить его свежую активность, удалив сокет из структуры и затем повторно вставив его с updated/current last_time/priority value), однако это также операция O(log(N)), поэтому накладные расходы не должны быть слишком высокими.