LINUX.ORG.RU

epoll и висящие соединения


0

3

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

как это сделать красивей чем периодический перебор всего списка соединений и сравнения текущего времени и времени соединения?

сокеты не блокирующие SO_RCVTIMEO не работает

★★★★

Heap из соединений по времени активности, т.е. обычный планировщик, в своём эвент лупе поможет.

mashina ★★★★★ ()

Открой man libev. Долистай до заголовка «Be smart about timeouts». Там опиcаны 4 приёма работы с таймаутами (в качестве концепции подходят к любой реализации не только к libev).

Кстати, советую libev и использовать для этих целей.

anonymous ()
Ответ на: комментарий от mashina

те в отдельном треде пробегаться по всем - понимать у кого вышел таймаут и что-то делать так?

соединений то может быть сотни тысяч

quest ★★★★ ()
Ответ на: комментарий от quest

Я думаю, раз в 5-10 секунд пройтись по сотне тыщ чисел не проблема. По крайней мере, оверхед мне видится совсем маленьким по сравнению с обработкой запросов от этих клиентов.

true_admin ★★★★★ ()

У меня в коде засыпание в epoll делается с таймаутом. Пускай для простоты, таймаут 1 секунда. В цикле, перед тем как вызвать сам epoll(), вызываешь для каждого класса обработчика какой-нибудь виртуальный метод, у меня Think(). В этот момент каждый обработчик думает, не истекли ли какие-нибудь таймауты. Таймаутов может быть много и на разные вещи. Отсутствие активности, это лишь один случай.

pathfinder ★★★★ ()

Если у тебя все обработчики одинаковые, то можно воспользоваться свойством упорядоченности у контейнера std::set<>. Написать свой Compare для упорядочивания по времени и использовать метод std::set<>::lower_bound(). Я правда так никогда сам не делал. Может там есть ньюансы.

pathfinder ★★★★ ()
Ответ на: комментарий от pathfinder

У меня в коде засыпание в epoll делается с таймаутом. Пускай для простоты, таймаут 1 секунда.

ты про epoll_wait? там некогда спать - представь что там сотни тысяч соединений - кто-то приходит, кто-то уходит, а кто-то тупо висит. вот висящих и нужно кильнуть.

все сокеты я ассоциировал с std::unordered_map по ней и буду пробегаться в отдельном треде. фигово то что добавление, удаление да похоже и сканирование карты нужно под мьютекс ставить, боюсь много локов будет.

quest ★★★★ ()
Ответ на: комментарий от pathfinder

epoll_wait отдает дескрипторы сокетов, а мне нужно ассоциировать с ними данные, поэтому std::unordered_map в которую я добавляю новые соединения и удаляю старые. а раз нужно в отдельном потоке пробегать по мапе то нужно ее делить между потоками - соответственно и мьютексы на мапу и на соединения

quest ★★★★ ()
Ответ на: комментарий от quest

зачем отдельный поток для обхода контейнера? проверка истечения таймаута создаст в 100500 раз меньшую нагрузку, чем обработка сокетов, поэтому если возникает проблема с производительностью, то нужно распараллеливать обработку

anonymous ()
Ответ на: комментарий от anonymous

проверка истечения таймаута создаст в 100500 раз меньшую нагрузку

ok - как понять что у сокета истек таймаут?

quest ★★★★ ()
Ответ на: комментарий от anonymous

вы как я понимаю предлагаете на каждый сокет вешать свой таймер. миллион сокетов - миллион таймеров.

quest ★★★★ ()
Ответ на: комментарий от quest

ok - как понять что у сокета истек таймаут?

ну, у тебя же не только сокет есть, а какой-то контекст связан с каждым сокетом — вот там и храни время последней активности
плюс обход всего контейнера: периодически, например, с каждым выходом из epoll() переходить к новому контексту и проверять его на истечение таймаута
здесь только нужно придумать такой итератор по контейнеру, который не сломается при вставке/удалении элемента

anonymous ()
Ответ на: комментарий от anonymous

ну, у тебя же не только сокет есть, а какой-то контекст связан с каждым сокетом — вот там и храни время последней активности

естественно это я описал выше

здесь только нужно придумать такой итератор по контейнеру, который не сломается при вставке/удалении элемента

в принципе то-же вариант но тогда при большой активности нужно наверно будет пропускать ходы, а при маленькой наоборот.

можно конечно тупо после обработки одного сокета - проверять на таймаут один сокет.

но если захотеть все это распараллелить то будет геморрой

quest ★★★★ ()
Ответ на: комментарий от anonymous

а как иначе?

пробегаться по множеству сокетов итератором, но не по всем а по одному за одно событие

quest ★★★★ ()
Ответ на: комментарий от quest

но если захотеть все это распараллелить то будет геморрой

хотя нет, очень не плохо все вырисовывается, тут проблема в том что по сокету может придти новое событие когда он в треде обрабатывается, это элегантно решается с помощью EPOLLONESHOT в epoll_ctl

quest ★★★★ ()
Ответ на: комментарий от quest

при большой активности нужно наверно будет пропускать ходы

можно, но зачем? от более частых проверок хуже не будет; нагрузка от проверки таймаута в сравнении с обработкой данных мизерная — уже обсуждали

а при маленькой наоборот

здесь нужно задаться каким-то лимитом на количество сокетов S, которые вся эта схема потянет гарантированно, и затребовать точность отслеживания таймаута dt, тогда полный обход контейнера нужно совершать каждые dt секунд, тогда каждые dt/S секунд нужно проверять новый сокет на таймаут; если максимальное время сна T в epoll() больше чем dt/S, то проверять за раз не один, а T*S/dt сокетов

после обработки одного сокета - проверять на таймаут один сокет

не только после обработки, а после каждого выхода из poll(), ведь данных может не быть какое-то время по всем сокетам

но если захотеть все это распараллелить то будет геморрой

почему?
каждый новый сокет передается в обработку отдельному треду/процессу, в котором своя независимая обработка всех сокетов, доставшихся этому треду, отдельный контейнер сокетов, и локальный обход только своих сокетов

anonymous ()
Ответ на: комментарий от anonymous

не только после обработки, а после каждого выхода из poll(), ведь данных может не быть какое-то время по всем сокетам

да я то-же об этом подумал

каждый новый сокет передается в обработку отдельному треду/процессу, в котором своя независимая обработка всех сокетов, доставшихся этому треду, отдельный контейнер сокетов, и локальный обход только своих сокетов

тред делает только кусочек работы, а потом должен опять вернуть сокет в общий epoll.

можно сделать пул тредов и в каждом свой epoll и после accept забрасывать сокет в один из тредов - вот тогда реально у каждого треда будет свое множество сокетов - каждый сокет от начала и до конца жизни будет ассоциирован с один и тем же тредом...

а как сделано в nginx? не так ли?

quest ★★★★ ()
Ответ на: комментарий от quest

ну точно, получается что и лочить ничего не нужно, только очереди заданий тредам, отлично! спасибо за наводку, а то я слишком в код зарылся)))

quest ★★★★ ()
Ответ на: комментарий от anonymous

угу, но когда мозг уже зашорен другой архитектурой, то лучше поздно чем никогда)

quest ★★★★ ()
Ответ на: комментарий от quest

epoll_wait отдает дескрипторы сокетов, а мне нужно ассоциировать с ними данные, поэтому std::unordered_map

O_o А я для ээээ.... ассоциации с данными использую поле data в структуре epoll_event. Оно для этого и предназначено. Туда можно засунуть указатель на свои данные, ассоциированные с дескриптором.

Или я опять не понимаю хитрость задумки?

pathfinder ★★★★ ()
Ответ на: комментарий от pathfinder

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

quest ★★★★ ()
Ответ на: комментарий от quest

Если не нравится постоянно обходить все множество обработчиков соединений на нахождение истекших таймаутов, то можно завести ordered set отсортированный по времени, который будет хранить объекты характеризующие время пробуждения того или иного обработчика. Назовем этот объект будильником. Добавляем будильники в set<> и в каждом цикле находим будильники с истекшим временем через метод lower_bound().

pathfinder ★★★★ ()
Ответ на: комментарий от pathfinder

1) слишком большой бессмысленный оверхед на извлечение и вставку элемента при каждой новой активности сокета
2) lower_bound() будет давать лишь самого вероятного кандидата, реальное истечение таймаута нужно все равно проверять явно

anonymous ()
Ответ на: комментарий от pathfinder

да мысль интересная, только скорее в map ведь нужно не просто время, а время связанное с сокетом

quest ★★★★ ()
Ответ на: комментарий от anonymous

2) lower_bound() будет давать лишь самого вероятного кандидата, реальное истечение таймаута нужно все равно проверять явно

Вроде нет, вызываем функцию с фейковым будильником, хранящим текущее время. Функция возвращает итератор, все элементы до него имеют истекшие таймауты.

Вообще хороший вопрос в плане оверхеда. В своих программах я тупо перебираю все обработчики, как предлагает анонимус, но я не имею дела с миллионом соединений.

pathfinder ★★★★ ()
Ответ на: комментарий от quest

те в отдельном треде пробегаться по всем - понимать у кого вышел таймаут и что-то делать так?

что-то совсем не понимаю в каком укуре можно было дойти до такого суждения.

На каждом витке обработки epoll'а смотришь в голову кучи и сравниваешь время активности этого элемента с текущим. Это O(1) операция, типичная топорная реализация планировщика.

Соответственно, после проявления активности соединене нужно засунуть в кучу планировщика с новым временем активности (куча «отсортирована» по времени) или обновить его положение. Это ~ log(N).

Далее возможны микрооптимизации, чтобы часто не делать log(N) после каждой активности соединение сначала ложим в LRU лист горячих коннектов. Если оно проявило активность и находится в LRU листе, то просто перемещаем его наверх листа. Это O(1) вместо log(N) для горячих коннектов.

И на каждом этапе обработки эвент лупа сначала смотрим вниз LRU листа и выкидывает из него холодные соединения в кучу (~ проявили активность более N сек назад). После чего обрабатываем кучу планировщика.

Если время таймаута одинаково для всех соединений, то достаточно просто иметь один LRU и периодически выкидывать из головы просроченные коннекты, т.е. все операции будут O(1).

mashina ★★★★★ ()
Ответ на: комментарий от mashina

+1

Тоже вот слежу за тредом и не понимаю «в каком укуре можно было дойти до такого суждения». Хотел сам написать советы, потом понял, что ТС всё равно с первого раза не поймёт, а объяснять мне лениво будет, и забил. Хорошо, что ты не поленился объяснить. :D

И вот эти люди пишут, судя по всему, высокопроизводительные сервера... Страшно за индустрию.

anonymous ()
Ответ на: комментарий от anonymous

«в каком укуре можно было дойти до такого суждения»

ТС настолько укурен, что за три года так и не понял, каким образом сервер может принять больше чем 65535 соединений на одном порту.

edigaryev ★★★★★ ()
Ответ на: комментарий от edigaryev

так и не понял, каким образом сервер может принять больше чем 65535 соединений на одном порту.

Я может из танка вылез, но разве есть способ клиентом с одного ip адреса организовать более 65535 соединений на один определенный ip:port сервера?

pathfinder ★★★★ ()
Последнее исправление: pathfinder (всего исправлений: 1)
Ответ на: комментарий от crowbar

А чего всех в LRU-листе не хранить?

Прочитай же сообщение полностью, до последней строчки.

На случай если лень читать, копипащу

Если время таймаута одинаково для всех соединений, то достаточно просто иметь один LRU и периодически выкидывать из головы просроченные коннекты, т.е. все операции будут O(1).

mashina ★★★★★ ()
Последнее исправление: mashina (всего исправлений: 1)
Ответ на: комментарий от pathfinder

Я может из танка вылез, но разве есть способ клиентом с одного ip адреса организовать более 65535 соединений на один определенный ip:port сервера?

почитай количество исходящих TCP соединений и дофига открытых сокетов

quest ★★★★ ()
Ответ на: комментарий от pathfinder

2011 год, ТС создает тред и спрашивает:

Подскажите пожалуйста что подтюнить чтобы клиенты могли создавать больше исходящих коннектов (и возможно и серверу этот лимит поднять так как боюсь он следующий на очереди)

Ему отвечают:

сервер хоть миллион соединений примет на ОДИН заданный порт

2014 год, ТС создает еще один тред и в нем интересуется:

я наверное что-то не понимаю... как вообще может быть на одной машине больше 65536 открытых сокетов?

На что ему отвечают:

открытый сокет это не порт+айпи. Это айпи+порт на одной стороне + айпи+порт на другой. Т.е. это комбинация четырёх чисел, а не двух.

И дальше начинается полная наркомания. Надеюсь что до него таки дошло.

edigaryev ★★★★★ ()
Ответ на: комментарий от edigaryev

в первой теме я увидел что изменение net.ipv4.ip_local_port_range повысило в моем тесте количество коннектов, однако выше 65535 параметр поднять было нельзя. поскольку логично что accept() создает новый сокет и мне эти годы больше 65535 и не было надо то дальше я не копал. не дошло до меня ничего, потестирую еще и сделаю вывод.

quest ★★★★ ()
Ответ на: комментарий от quest

однако выше 65535 параметр поднять было нельзя. поскольку логично что accept() создает новый сокет

ТС видимо до сих пор не понимает разницу между сокетом и портом?

anonymous ()
Ответ на: комментарий от anonymous

еще раз: изменение параметра связанного с диапазоном портов дало увеличение принятых сокетов (возможно я и ошибся тогда), однако вполне логично предположить число 65535 для принятых сокетов. количество pid всех процессов вон вообще 32768 (по крайней мере в дефолтной конфигурации)

quest ★★★★ ()

как это сделать

проблемы бы не возникло, если бы программа была бы ИНЗАЧАЛЬНО написанна бы ПОЛНОСТЬЮ через неблокирующее функции (включая управление таймерами), используя какой-нибудь фреймворк для этого дела.

а решением стало [это] — так как это и есть стандартый алгоритум находящийся внутри любого Async I/O фреймворка.

сокеты не блокирующие SO_RCVTIMEO не работает

ксате не так давно я создавал тему про SO_KEEPALIVE :-) .. хотя и не факт что это здесь точно подошло бы (два часа — это слишком долго)

user_id_68054 ★★★★★ ()
Последнее исправление: user_id_68054 (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

anonymous ()
Ответ на: комментарий от anonymous

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

anonymous ()

Проверку коннекшенок лучше делать по таймеру, создай timerfd и добавь его в epoll, будет получаеть EPOLLIN и по нему проверяй сколько пакетов получил по каждому коннекшену, если скажем несколько циклов пакетов не было - разрываешь соединение

pulo ()
Ответ на: комментарий от pulo

интересная фигня спасибо! наверное это самый быстрый способ завести отдельный epoll на таймеры и выбирать сработавшие, нужно потестить на миллионе

quest ★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.