LINUX.ORG.RU

Как заставить linux,epoll передавать управление программе после каждого полученного tcp сегментa?

 , ,


1

3

Есть tcp подключение к серверу. Сервер «выплевывает» сообщения по одному, tcpdump -ом видно что одно сообщение - один tcp сегмент. Между сообщениями 70мкс. Но linux + epoll «объединяют» по три или даже четыре прилетающих tcp сегмента в один и только после этого передается управление в программу для чтения буфера.
Что вызывает задержку для принятия решения 210мкс и более.

С какими флагами нужно создавать сокет и вызывать epoll, чтобы каждый tcp сегмент оказывался в программе?

★★★★

Последнее исправление: Vlad-76 (всего исправлений: 2)
Ответ на: комментарий от firkax

Программа пока только читает буфер сокета и выводит его на экран, думаю что printf считанного буфера и функция получения времени от linux не может внести задержку близкую к 70мкс. В программе в цикле ожидается возврат epoll_wait и после читается буфер. Ладно бы на старте такое поведение случилось бы разок, так происходит постоянно.

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

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

делайте как делают все

в каждом отправляемом пакете первым отправляете размер сообщения

на принимающей стороне принимаете первый recv в котором знаете размер, дальше думаю все понятно

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

думаю что printf считанного буфера и функция получения времени от linux не может внести задержку близкую к 70мкс

Может ещё как. Поставь clock_gettime(CLOCK_MONOTONIC) непосредственно перед epoll и сразу после него и выводи оба числа в printf - увидишь сколько мкс потрачено на epoll.

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

«на принимающей стороне принимаете первый recv в котором знаете размер, дальше думаю все понятно» не понятно. epoll_wait передает управление программе после накопления в сокете данных от 3х-4х tcp сегментов, т.е. задержка уже накопилась.

Vlad-76 ★★★★
() автор топика
Ответ на: комментарий от Vlad-76

конкатенация сегментов это внутренняя машинерия приема на уровне ядра

вы не можете ее контролировать

поэтому если нужно видеть и прерываться на сегментах

нужно спускаться на уровень tcpdump, открывать bpf интерфейс или какой он там открывает

но если у вас приложение верхнего уровня и вы работаете с данными которые передает tcp

и вам всего лишь нужно более часто прерываться

то ответ - никак

можете попробовать без epoll

в цикле recv крутить и по таймеру прерываться

anonymous
()

а разве TCP_NODELAY не для этого придумали? что-то вроде такого не работает?

setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));

ps.

ссылка с такой же строкой https://stackoverflow.com/questions/8421848/send-data-in-separate-tcp-segments-without-being-merged-by-the-tcp-stack

vtVitus ★★★★★
()
Последнее исправление: vtVitus (всего исправлений: 2)

Но linux + epoll «объединяют» по три или даже четыре прилетающих tcp сегмента в один и только после этого передается управление в программу для чтения буфера.

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

Что вызывает задержку для принятия решения 210мкс и более.

Мне кажется, что при таких жёстких требованиях к latency имеет смысл переходить на UDP

annulen ★★★★★
()

Чтобы сокет наконец начал работать как нужно и передавал каждый TCP-сегмент без лишних задержек, попробуйте, наконец, отключить алгоритм Нейгла с помощью TCP_NODELAY, включить режим Edge-Triggered (EPOLLET) в epoll, и уменьшить размер сокетных буферов. Не забудьте также активировать TCP_QUICKACK, чтобы избавиться от ненужных задержек с отправкой ACK. А то пока вы тут медлите, ваш сервер сам все пакеты перекрутит в один и подарит вам 210 мкс задержки, с которыми и возиться будете.

=ваш робот

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

Так же ведет. сегменты объединяются в один.

задержка между отправкой и получением пакета какая?

алгоритм проверки:

  • устанавливаем соединение
  • на клиенте уходим в блокирующий recv, причем получаем данные размером равным размеру отправленного пакета
  • отправляем данные, с выключенным алгоритмом Нагла (опция TCP_NODELAY)
  • замеряем, смотрим
olelookoe ★★★
()
Ответ на: комментарий от olelookoe

recv так себя конечно ведёт, если к моменту его вызова уже пришли три сегмента. Автор считает что он слишком поздно вызывает recv из-за того что epoll слишком поздно его оповещает. Но проверять все тайминги (я ему сказал как) автор не хочет, так что возможно он где-то ошибается и не узнает об этом.

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

recv так себя конечно ведёт, если к моменту его вызова уже пришли три сегмента. Автор считает что он слишком поздно вызывает recv из-за того что epoll слишком поздно его оповещает.

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

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

возможно он где-то ошибается и не узнает об этом.

да х его з что там происходит на самом деле, свидетелям не верю, у них глаза на жопе и мысли о высоком - «ой, а я смотрел не на поезд а на сиськи, я ж не знал что надо было номер поезда, а не сисек, а сиськи я точно запомнил, четвертый!»
поэтому ни в какие заявленные tcpdump-ы я не верю

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

а то все эти «это epoll виноват, это ядро виновато, это все они виноваты!», «опциями его, опциями!», «лошадью ходи, век воли не видать!» не к просветлению нас ведут, очевидно, Лао Цзы бы не одобрил

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

В программе должно создаваться до 5000 tcp подключений в сек и асинхронно обрабатываться ответы этих подключений. Ни одно из подключений не должно блокировать (ждать появления данных на чтение) другие подключения. В каком подключении появились данные на чтение то и начинаем обрабатывать.

Vlad-76 ★★★★
() автор топика
Ответ на: комментарий от firkax

Не блокирующий recv вызывал в бесконечном цикле с задержкой 1 мкс между вызовами, т.е. 70 раз за 70мкс. загрузка ядра процессора при этом 16%.

тайминги я проверял, один раз поймал задержку 140мкс, было «слеплено» два tcp сегмента. во всех остальных случаях 210 мкс.

Пробовал дополнительно перед recv вызывать - «используй просто ioctl(fd, FIONREAD, & count) получишь размер данных, нет данных сиди жди.»

Иногда проскакивало что на момент вызова ioctl в буфере сокета был сайз одного сообщения, но следом вызванный recv уже читал слепленные tcp сегменты, в лучшем случае бывало два сообщения.

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

В программе должно создаваться до 5000 tcp подключений в сек и асинхронно обрабатываться ответы этих подключений. Ни одно из подключений не должно блокировать (ждать появления данных на чтение) другие подключения. В каком подключении появились данные на чтение то и начинаем обрабатывать.

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

annulen ★★★★★
()
Ответ на: комментарий от Vlad-76

в чем смысл прерываться на сегментах?

вы считаете что сегмент = сообщение которое отправлено?

тогда спешу вас разочаровать

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

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

где воркер разгребает и перекладывает в другую очередь

и все это локфри

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

«вы считаете что сегмент = сообщение которое отправлено?» считаю и уверен.

Т.к. один tcp сегмент - это одно (законечнное логически) сообщение, то мне и нужно чтобы отдельно каждое сообщение обрабатывалось программой с частотой отправки.

Речь не про загрузку проца вообще. Даже 1 раз в 1 мкс считывая буфер сокета, не получается считать один tcp сегмент.

про потоки мне рано думать, я надеюсь

Vlad-76 ★★★★
() автор топика
Последнее исправление: Vlad-76 (всего исправлений: 2)
Ответ на: комментарий от olelookoe

как только мы отравим пакет (+ время доставки) блокирующий сокет клиента получит данные, отлипнет и мы узнаем всю правду.

Нет, не «как только», а спустя время пока пакет пройдёт через сетевой стек отправителя, через кабель и через сетевой стек приёмника, а так же когда планировщик приёмника выделит время процессу. И, поскольку у автора речь идёт о разнице в 100мкс, все эти задержки важные и нельзя ими пренебрегать.

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

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

«И поять повторю - сделай нормальные замеры времени везде а не тыкайся наугад.» как мне кажется я сделал это нормально при использовании epoll_wait - до вызова epoll_wait и сразу после вызова epoll_wait. Когда летят 4 ре последовательных tcp сегмента, все хорошо видно , к тому же видно по выводу содержимого буфера - объединение содержимого сегментов.

Vlad-76 ★★★★
() автор топика
Ответ на: комментарий от Vlad-76

Пробовал дополнительно перед recv вызывать - «используй просто ioctl(fd, FIONREAD, & count) получишь размер данных, нет данных сиди жди.»

Не слушай глупые советы. И чушь про TCP_NODELAY на всякий случай тоже не слушай.

Повторю ещё раз что надо сделать. Делаешь для удобства такую функцию:

unsigned long get_us(void) {
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return (unsigned long)ts.tv_sec*1000000 + ts.tv_nsec/1000;
}
Перед epoll_wait (или как там его) делаешь t0=get_us();, между ним и recv() делаешь t1=get_us();, после recv() делаешь t2=get_us();. Потом где-то делаешь printf("times %lu %lu\n", t1-t0, t2-t1);

Вот глядя на эти числа уже можно будет думать кто виноват. Только тут ещё одна подстава - printf вполне легко сожрёт эти микросекунды на печать символов. Возможно, стоит сначала сохранить все эти тайминги в массив а потом в конце работы (спучтся пару секунд) его целиком вывести на экран.

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

пока что тупняк постишь ты

ТС пытается строить логику прикладного уровня, основываясь на логике транспортного уровня (TCP) - это бред

многие в теме пытаются играться с какими то опциями сокета - очередной бред

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

«вы считаете что сегмент = сообщение которое отправлено?» считаю и уверен.

Это не так. Если отправитель будет непрерывно спамить небольшими сообщениями, то его система вполне может объединить какое-то их кол-во и передать одним фреймом, потому что tcp - это потоковая передача и никакого разделения на сообщения в нём не предусмотрено. Если нужно разделение, то используй udp.

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

У него возможно отправитель это какая-то железка без ОС и сегменты она формирует так, как формирует. Или ещё почему-то они получаются именно такие. Но какая в целом разница? Что ты к этому разделению то прицепился? Проблема не в том что он прочёл сразу три сегмента, а в том, что из этих трёх только третий актуальный а два остальных просроченные на около 100мкс.

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

Он хочет невозможного. А если первое сообщение из трех потеряется? Он действительно считает, что сможет прочитать оставшиеся до того, как первое сообщение будет повторно отправлено и дойдет до адресата?

Нужно разделение на датаграммы на уровне протокола? Пусть использует udp. Но придется самому городить защиту от потери сообщений, контроль правильной последовательности и т.п.

anonymous
()