вторник, 14 декабря 2010 г.

Мой опыт использования epoll в edge-triggered режиме.

Довелось писать сервер, у которого была необходимость поддерживать соединения с подключенными клиентами, посему решил использовать epoll. У epoll есть два режима работы: edge-triggered (ЕТ) и level-triggered (LT), чем они отличаются описано в man-странице. Если использовать его в LT, то это просто более быстрая версия poll'а. Но хотелось использовать этот механизм по полной, тем более что в ЕТ лучше работать с неблокируемым сокетами, тем более что это отличная возможность попрактиковаться в использовании оных.
Итак, приступил к написанию. Читать из сокета в таком случае стоит до тех пор, пока мы не получим EAGAIN. Хорошо, вроде все понятно, написал следующий код:

do {
        n = recv(sock, temp, sizeof(temp), 0);
} while (n < 0 && errno == EAGAIN);


До поры до времени решение работало нормально, но, когда появились клиенты с медленным (я тогда еще не знал, что соединение у них медленное), то этот цикл становился уж больно долгим и я не мог дождаться ответа... В голову пришло два решения:
1. Не выпендриваться, поставить свою самооценку на место и сделать все в LT через обычные блокируемые сокеты;
2. Вставить счетчик цикла и при достижении какого-то значения посылать клиента подальше.
Ладно, выбрал первый вариант. Но, через какое-то время опять столкнулся с аналогичной ситуацией - клиент вроде как подключился, но поток все равно блокировался на довольно большой промежуток времени. Хм... Взял в руки wireshark и начал смотреть какие пакеты ко мне приходят в надежде выяснить причину. Беру клиента, подключаюсь наблюдаю за пакетами: syn, syn-ack, ack, пауза ... и ... у меня в голове загорается лампочка! Ну конечно же! epoll_wait срабатывает как только проходит трехэтапное квитирование (handshaking)! Возвращаюсь к первоначальному варианту (неблокируемые сокеты в ЕТ) и повторяю процедуру заново. Вот оно! Как только tcp-соединение физически установлено начинает работать этот цикл и крутится до тех пор, пока не получит хотя бы первую порцию данных (а потом опять крутится, пока не получит все данные), а не получать он ее может довольно долго, если у клиента медленное соединение (c gprs вобще мрак).
В итоге пришла в голову идея - если использовать второе решение (со счетчиком цикла), то по достижении некоего порога клиентов с медленным соединением можно отбрасывать (если у вас сервер обрабатывает подключения в один поток, а из-за медленных клиентов вам не хочется задерживать отсальных).  Однако тут есть и минус - мы получаем активное ожидание, которое, при достаточно высокой нагрузке может положить сервер любой мощности (были прецеденты на прошлой работе).
Традиционная часть, которую всегда пишут в начале - для чего писал статью? Во-первых, поделиться опытом с коллегами, если тем самым ответил на чей-то вопрос или помог понять что-то более глубоко, то я достиг своей цели. Во-вторых, данная статья может послужить поводом для дискуссии и, что будет просто замечательно, целью для критики со стороны более опытных коллег, ведь ничто не учит нас жить так хорошо, как хорошая порка. Спасибо, что не ушли с этой страницы сразу ;)