LINUX.ORG.RU

Пинг-Понг пересылка для поддержки конекта

 


0

0

Приветствую участников форума! Вопрос по большей части относительно фаерволов.
Разрабатывается простой клиент-серверный сервис, клиент подключается по протоколу tcp к этому сервису и устанавливается соединение, но как-бы данные не пересылаются, а просто клиент ожидает поступления данных от сервера.
Стоит ли ожидать каких либо действий со стороны фаервола в отношении таких соединений?
Думаю сделать типа пинг-понг пересылку с интервалом для ожидающих клиентов до поступления целевых данных, потом как только пересылка важных данных закончена, опять переводить в пинг-понг режим до поступления очередной порции данных.

Ответ на: Дык... от Moisha_Liberman

Второй момент:

А тут вон, человек пишет про сервер, который рвёт соединения раз в минуту аки Тузик грелку. Ну... Второго такого совпадения я уже просто не мог пропустить. =)))

Тут речь скорее про нештатные ситуации, например - если QoS у хитрого роутера сглючил и он профукал пару пакетов. Или, например когда произошёл сбой по питанию в интересный момент. В проде такое ловится крайне редко, но когда ловится кастомер разбрызгивая слюной вопит какого дьякона у меня ваше убогое ПО зависло на 2 часа, а народ чешит репу ибо в логах всё как бэ ок, прост данных от сервера небыло, а сервер - клянётся мамой, что слал изо всех сил, и если сервер продуман, прикладывает tcp дампы.

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

Это тогда к реализации протокола.

Потому что если взять тот же http/s, то там есть поле Content-Length. Т.е., сервер и клиент оба знают сколько байт должно быть принято-передано. И, если что-то пошло не так, то клиент должен перезапросить данные и ему они должны вернуться в полном объёме. Если опять не вернулись, то опять перезапросить. В таком случае даже если промежуточный роутер профукает пару пакетов, то клиент будет точно знать что перезапрашивать надо. У него объём данных не сойдётся в сумме.

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

Если сервер не сказал сколько именно данных он должен вернуть в ответ на запрос клиента, то всё плохо. Тут всё самое весёлое и начинается. Клиент не будет знать что ему делать в каком случае.

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

Не...

send() не занимается ожиданием ACK. Он просто записывает данные в буфер если может, либо сигнализирует об ошибке на своей стороне. А вот вся остальная работа, она за кадром. Но send() ни чего не сможет записать в буфер если будет состояние ошибки. Это состояние берётся именно из реализации стека tcp. Из его внутреннего состояния, которое из user space мы не видим напрямую, только косвенно, через send().

Moisha_Liberman ★★
()
Ответ на: Не... от Moisha_Liberman

Да. Тогда непонятно об чём толкс, вроде все всё одно и то же сказали, но имеет место быть дискуссия :)

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

Про пруф.

Вот смотри. У тебя есть сервер. Сервер должен отдать файл. Пусть будет 20М, пофиг.

Сервер отдаст весь файл одним пакетом? Нет. Даже в случае использования ф-ии sendfile(), которая по идее для этого и предназначена, файл будет передаваться более мелкими кусками. sendfile() просто облегчает нам жизнь и не более, т.к. у нас нет кучи read()/write() или send(). Но основная работа производится сходным в принципе образом.

Предположим, у нас её нет (sendfile()) и мы делаем как в старые времена. При получении запроса на файл, открываем его, считываем сколько-то данных в буфер через read() и кидаем их в открытый сокет через send() или write(). И так пока все данные файла не будут переданы.

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

Дальше на стороне клиента мы читаем данные через recv()/read() и полученное число байт складываем с нарастающим итогом. В сумме мы должны получить число байт, которые должны быть считаны. Если recv()/read() возвращает -1, то это повод сбросить результаты считывания и пересоздавать соединение заново, запрашивать файл заново и, короче, всё снова и заново.

На стороне сервера, если send() возвращает ошибку, то это повод просто сбросить соединение, тут ждать уже нечего.

В твоей же ссылке чувак всё верно говорит:


Yes send/recv are called in a loop. But I assume that if send() is successful, that data should reach destination TCP. (and subsequently to application through 1 or more recv() Will post code in short time. – Rohan Dec 11 '09 at 6:51

Само по себе состояние ошибки, которое возникает в tcp, это берётся из более нижнего уровня, из kernel space. И ошибка тут будет (для send()) если send() ни чего не может передать в kernel space. Т.е., либо при установлении соединения connect() не сработал, либо при передаче пакетов возникла ошибка, ACK не вернулся, файл не передан и похрен сколько байт было передано до того, как.

В случае с файлом на 20М, предположим, мы кидаем его через ethernet. Округлённо мы берём размер eth frame в 1500 байт. Пофиг что с VLAN это 1522 байта, а без 1518, нам пофиг, округлённо.

По идее, 20 000 000/1500 мы должны получить 13333 пакетов. Предположим, мы передали успешно 13332 пакета, а в последнем произошёл сбой. Мы передали весь файл? Нет. Ошибка из кернел спейс в юзер спейс возвращена? Да. Ну вот и всё снова значит. И похрен что мы успешно влили 19998000 байт. Последние 1500 байт были с ошибкой, соединение разорвалось. И пофиг что они были в kernel space, т.к. в ответ стек tcp возвратил ошибку, т.е. внутренний механизм tcp (конкретнее tcp retransmission) не сработал, к сожалению.

Вот как-то вот так. Извини что так длиннО получилось...

Moisha_Liberman ★★
()
Ответ на: Это тогда к реализации протокола. от Moisha_Liberman

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

Это если не говорить про rpc например. Особенно с коллбэками, хотя и без колбэков всё так же только проблема будет только на стороне сервера.

Тут либо выставлять кастомный keep-alive и свято верить что твоё портабельное решение заведётся на любой консервной банке(ведь любая банка полностью поддерживает все открытые стандарты и их опциональные фичи), либо знать что оно будет работать на заданном наборе консервных банок(привет нефункциональные требования и их сбор до проектирования архитектуры) либо не париться и сделать хартбиты в протоколе, тогда (особенно актуально в свете всяких IoT) хрен с ним со стеком, на прикладном уровне всё решено.

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

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

А ты почитай чё там этот крендель выписывал...

У него там вообще чуднЫе сказки получались.

Я только добавлю один масенький штришок. Занафига нам эти два часа нужны. Если у нас прошёл успешный сеанс связи, данные обработаны, соединение закрыто нормальным образом, то чтобы просто ускорить возможное соединение в будущем, данные об успешном соединении и хранятся пару часов. В принципе, их можно оттуда зачищать, но это уже не столь суть. Это обычно ни кого не парит.

Если всё-таки нам нужен keep-alive, то он считается по 2 часа с момента либо последнего ACK, если у нас был успешный сеанс связи, но мы его не закрыли, либо с момента последней посылки keep-alive. Но тут уже всё определяется настройками. Файловый дескриптор-то занят будет. А их число хоть и unlimited можно выставить, но оно же не бесконечное на самом деле. И их может перестать хватать даже на файловые операции в самой системе, а висящих бестолку будет море. Вот и настраивают так, чтобы был разумный компромисс...

Moisha_Liberman ★★
()
Ответ на: Про пруф. от Moisha_Liberman

Плохой пример, но с большими пакетами данных такая ситуация(залипон на keep-alive), хоть и менее вероятна, но имеет место быть.

Вот допустим у тебя есть фид с данными(ну пусть те же презренные sms). Ты подключился к серверу, авторизовался, на этом ваши взаимные движения кончились(дальше клиент только читает). Sms лезет в mtu, и вообще для наглядности - выравнивается по `mtu - <служебные заголовки>`.

Ситуация когда ты будешь ждать отработки механизма keep-alive весь таймаут:

Для read(на стороне клиента заблокируется на keep-alive):

  • Сервер послал последнее sms из своей очереди
  • Ты его принял
  • Ты послал ack
  • Сервер умер без fin/fin не долетел по другой причине
  • Ты вызвал read(заблокировался на keep-alive)

Для write (на стороне сервера, следующий send заблокируется на keep-alive):

  • Сервер послал последнее sms из своей очереди
  • Ты его принял
  • Ты послал ack
  • Ты умер без fin/fin не долетел по другой причине
  • Сервер послал ещё одно sms (вызвал send)
  • Сервер послал ещё одно sms (вызвал send, заблокировался на keep-alive, после того, как переполнил буфер)
pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 7)
Ответ на: Не... от Moisha_Liberman

send() не занимается ожиданием ACK

Т.е., число переданных байт (> 0) это и есть значение, возвращаемое в user space функцией send(). В мане так и написано. Значит, ACK на переданную порцию данных получен.

Где-то тут противоречие. Как же ACK получен, если send() его не ждёт?

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

Всё просто.

send() ни чего не знает об ACK, FIN и всём прочем. send() либо может, либо не может передавать данные. Не более того. Это мы знаем и о kernel space и о процессах, там происходящих.

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

Да.

Вот только для консервных банок (IoT в общем и целом) есть MQTT, который поверх tcp реализован и MQTT-SN (для sensor networks который), который может даже без TCP (по радиоканалу, тому же ZegBee) фигачить. Если по модели TCP/IP, то предполагается, что MQTT-SN будет работать в пространстве протоколов, основанных на UDP. Т.е., весь контроль за доставкой и целостностью на программисте. Но тут уже вопрос что выгоднее в каждом конкретном случае. Либо консервная банка имеет ресурсы для более «жадного» до ресурсов TCP, либо простецкий UDP, если есть абы какое сетевое соединение, а не голый радиоинтерфейс.

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

По идее да, но...

тут всё зависит от конфигурации хоста. Для веб-серверов общего назначения частенько ограничивают число файловых дескрипторов через время их жизни. Т.е., простаивает без дела дескриптор какое-то время, на фиг, вырубить его. Иначе могут возникнуть подозрения на DoS/DDoS. Например, см. slowloris и производные. Задача в том, чтобы открыть как можно больше соединений с веб-сервером и как можно дольше их удерживать в открытом состоянии.

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

Не. Ты блокироваться на keep-alive...

в принципе не должен на продолжительное время в твоём случае.

Во-первых, сам по себе keep-alive для tcp сокета включается как-то так:

   optval = 1;
   optlen = sizeof(optval);
   if(setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
      perror("setsockopt()");
      close(s);
      exit(EXIT_FAILURE);
   }

Во-вторых, у тебя есть оции, которые в твоём случае лучше бы подкорректировать. Я про: cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 (вот эти те самые два часа)

cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 (интервал между посылками keep-alive в секундах)

cat /proc/sys/net/ipv4/tcp_keepalive_probes 9 (число посылок keep-alive перед тем, как kernel space признает соединение помершим и уведомит об этом user space)

Т.е., подвисший коннект будет убит через 2 часа, плюс, примерно 11 минут. 9 попыток по 75 секунд. Для узкоспециализированного сервера (той же реализации SMPP), критичного к задержкам, боюсь, это неприемлемо.

Эти значения в случае узкоспециализированного сервера, критичного к задержкам, лучше подтёсывать по месту. Либо через sysctl, либо через:

echo 600 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 60 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 20 > /proc/sys/net/ipv4/tcp_keepalive_probes

Ну либо своим кодом типа так:

   /* Это сработает ТОЛЬКО после включения SO_KEEPALIVE на сокете. По числовым значениям optval понятно что именно в какой опции я перезаписываю и как это соотносится с кодом в echo. */
   optval = 20;
   optlen = sizeof(optval);
   if(setsockopt(s, SOL_TCP, TCP_KEEPCNT, &optval, optlen) < 0) {
      perror("setsockopt()");
      close(s);
      exit(EXIT_FAILURE);
}

   optval = 600;
   optlen = sizeof(optval);
   if(setsockopt(s, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) < 0) {
      perror("setsockopt()");
      close(s);
      exit(EXIT_FAILURE);
}

   optval = 60;
   optlen = sizeof(optval);
   if(setsockopt(s, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) < 0) {
      perror("setsockopt()");
      close(s);
      exit(EXIT_FAILURE);
}

Но тогда, до кучи, т.к. это узкоспециализированный и критичный к задержкам сервак, я бы ещё и /proc/sys/net/ipv4/tcp_retries1 с /proc/sys/net/ipv4/tcp_retries2 подтёсывал бы по месту.

Эти оба параметра относятся к tcp retransmission. Первый параметр это время в секундах, после которого стек TCP начинает что что-то не так, из-за неподтверждённых посылок RTO. А второй параметр, это время, в течении которого при неподтверждённых посылках RTO, соединение считается живым. Здесь дефолтное значение в 15 даёт отсрочку для соединения в 924.6 секунды до того, как соединение будет сброшено. Оба значения выставлены по дефолту согласно RFC 1122, причём последнее не рекомендуется ставить менее 8 (что даёт 100 секунд для таймаута). Посмотреть таймеры соединений можно по netstat -tap --timer. Т.е., для случая хитро... выкруженного сервака я бы смотрел ещё и на параметры tcp retransmission.

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

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