Здравствуйте, AlexGin, Вы писали:
AG>Что здесь не так? AG>Я использовал некоторое подмножество вариантов применения TCP. AG>Да, может возможно было бы более рационально использовать пропускную спрсобность канала. AG>Но это не имеет актуальности.
Не надо при проектировании framing'а полагаться на то, что фреймы эти будут использоваться строго в режиме вопрос-ответ. Ничего особо не выигрываешь, а лишь смешиваешь два логически не связанных уровня. О чем потом и пожалеешь.
Я даже не о пропускной способности канала сейчас говорю, а о логической целостности твоего протокола.
Здравствуйте, vsb, Вы писали:
vsb>Да, тут сложней. Решение есть, даже два. Либо websockets, либо HTTP/2. Ну или делать polling, на самом деле тоже не самый плохой вариант. Но такое сам не пробовал и это уже посложней. Моя реплика в первую очередь относилась к модели запрос/ответ. Если нужно со стороны сервера инициировать сообщения, тут уже не всё так однозначно, соглашусь.
О, так и знал, прочтя твое первое сообщение про HTTP, что дойдет и до новомодного HTTP/2
Что до вебсокета, он превращает HTTP в, практически, TCP-сокет с навешенным на него фреймингом. В терминах HTTP это называется protocol upgrade
Здравствуйте, Pzz, Вы писали:
Pzz>MTU — это параметр сетевого интерфейса, который определяет максимальный размер пакета (все равно какого, TCP, UDP, ICMP...), который не будет фрагментироваться при отправке через этот интерфейс.
Для TCP важен минимальный из всех MTU по дороге.
Pzz>TCP, естественно, не хочет, чтобы его пакеты фрагментировались. Поэтому при соединении по локальной сети TCP будет ориентироваться на MTU, за вычетом служебных данных. А вот если соединение нелокальное, то у TCP нет знаний о том, как будет происходить фрагментация на промежуточных узках. Поэтому он будет исходить из максимального размера пакета, доставка которого без фрагментации гарантируется IP-протоколом. А это 576 байт для IPv4, и 1280 для IPv6.
Начинает с MTU исходящего интерфейса и корректирует по поступлению ICMP needfrag сообщений. Это во всех стеках. В Linux, да, есть дополнительный режим "если та сторона не подтверждает — пробуем уменьшать размер нагрузки".
Здравствуйте, AlexGin, Вы писали:
AG>Это только в теории... AG>Впрочем, я также думал до недавнего времени. Пока не столкнулся с ситуацией: AG>Когда сервер и клиент — на одном и том же компе — всё работает как швейцарские часы. AG>Но вот если сервер и клиент — соединены проводом и через хаб — как это в реальной жизни — то тут начинается практика...
В чём эта "практика"? Если вы учитываете, какими порциями смог прочитать recv() на приёмнике — вы уже успешно выстрелили себе в ногу. Не надо так.
Я бы на твоем месте, передавал блоки данных с длинной в заголовке блока (чтобы не парсить в поисках маркера конца, кроме того, с маркером ты не сможешь передавать в данных последовательность, используемую для маркера). Про MTU и все что лежит ниже уровня TCP тебе лучше сейчас вообще забыть, т.к. оно не влияет на передачу данных твоего приложения. То, что ты когда-то не смог найти маркер конца пакета и решил, что данные были изменены по пути — скорее всего ошибка в твоем приложении. Про то, что TCP потоковый протокол, тебе уже рассказали.
В общем, передавай данные блоками с длинной в заголовке. Читай до тех пор, пока не получишь весь блок целиком (тут нужно будет обрабатывать возможность отключения клиента и, возможно, другие ошибки типа таймаута). И все будет у тебя работать, как надо.
Маркер тебе не нужен, если очень хочется проверить целостность данных — используй контрольную сумму в виде sha256 (опять же в заголовке или в хвосте данных). Еще большую надежность даст TLS, но тогда придется заморочиться с сертификатами и чуть увеличится время подключения (на 1RTT/время туда-сюда). Накладные расходы на шифрование и дополнительные копирования ничтожны, если только ты не делаешь сервер Netflix (а это точно не так).
P.S. Если захочешь разобраться с MTU и как там оно устроено на более низких уровнях — это всегда пожалуйста, спрашивай. Но сейчас для твоего прикладного протокола тебе это не надо.
P.P.S. У TCP есть включенный по умолчанию алгоритм минимизации количества пакетов (гуглить по "TCP_NODELAY"). Когда TCP пакет сформирован не целиком (записали 10 байт, например, а в TCP пакет влезает 1460, или записали 10K и в последнем пакете еще осталось место), то пакет ждет несколько ms, вдруг появятся еще данные, чтобы их тоже отправить в том же пакете (целиком сформированные пакеты пойдут в сеть сразу, им нет смысла ждать). Тебе, возможно, полезно отключить эту задержку, чтобы при записи в сеть блока данных, он тут же начал отправляться без задержки.
Здравствуйте, netch80, Вы писали:
N>Начинает с MTU исходящего интерфейса и корректирует по поступлению ICMP needfrag сообщений. Это во всех стеках. В Linux, да, есть дополнительный режим "если та сторона не подтверждает — пробуем уменьшать размер нагрузки".
Я не стал рассказывать про MTU path discovery, чтобы чрезмерно не переусложнять тему.
Я где-то читал, что эти needfrag не всегда доходят и не всегда посылаются, поэтому с ними бывают всякие разные проблемы. Не помню, включен ли MTU path discovery в линухе по умолчанию, а проверять лень...
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, vsb, Вы писали:
vsb>>У IPv4 контрольная сумма защищает только заголовок. У IPv6 по-моему вообще её нет.
Pzz>Там две контрольные суммы. Одна защищает заголовок, другая — тело TCP-пакета. И обе одинаково паршивые.
1. Вот та, что защищает заголовок IP-пакета, в IPv6 отменена. Осталась контрольная сумма TCP.
2. Пусть паршивые, но их ортогональность типичной контрольной сумме Ethernet (которая CRC32-CCITT) очень помогает.
Здравствуйте, Reset, Вы писали:
R>P.P.S. У TCP есть включенный по умолчанию алгоритм минимизации количества пакетов (гуглить по "TCP_NODELAY"). Когда TCP пакет сформирован не целиком (записали 10 байт, например, а в TCP пакет влезает 1460, или записали 10K и в последнем пакете еще осталось место), то пакет ждет несколько ms, вдруг появятся еще данные, чтобы их тоже отправить в том же пакете (целиком сформированные пакеты пойдут в сеть сразу, им нет смысла ждать). Тебе, возможно, полезно отключить эту задержку, чтобы при записи в сеть блока данных, он тут же начал отправляться без задержки.
Вроде бы, современные TCP-стеки понимают, что если после одного или нескольких send() сказали recv(), то можно больше и не задерживать хвост последнего пакета, все равно, пока recv() чего-нибудь не вернет, send()ов больше не будет.
Здравствуйте, Pzz, Вы писали:
N>>Начинает с MTU исходящего интерфейса и корректирует по поступлению ICMP needfrag сообщений. Это во всех стеках. В Linux, да, есть дополнительный режим "если та сторона не подтверждает — пробуем уменьшать размер нагрузки".
Pzz>Я не стал рассказывать про MTU path discovery, чтобы чрезмерно не переусложнять тему.
Можно было вынести в сноску. Совсем это игнорировать нежелательно — обычно именно где-то посредине дороги и возникают туннели с урезанным MTU.
Pzz>Я где-то читал, что эти needfrag не всегда доходят и не всегда посылаются, поэтому с ними бывают всякие разные проблемы. Не помню, включен ли MTU path discovery в линухе по умолчанию, а проверять лень...
Включен. Не всегда посылаются — да, бывает, но последние лет 10 я про такое уже не слышал. Может, даже самые тупые и ленивые наконец починились...
Здравствуйте, уважаемый netch80, Вы писали:
N>В чём эта "практика"? Если вы учитываете, какими порциями смог прочитать recv() на приёмнике — вы уже успешно выстрелили себе в ногу. Не надо так.
Ну хорошо — я накопил данные от первого recv(), затем следующего и т.д — пока не уяснил что блок_пользовательских_данных принят весь.
Блок_пользовательских_данных — это тот полный набор данных, что следует от сервера к клиенту.
Таким образом, если при передаче по сети его разбило на несколько частей, то я их могу "накопить" и соединить вместе.
Так нормально?
Здравствуйте, Pzz, Вы писали:
vsb>>У IPv4 контрольная сумма защищает только заголовок. У IPv6 по-моему вообще её нет.
Pzz>Там две контрольные суммы. Одна защищает заголовок, другая — тело TCP-пакета. И обе одинаково паршивые.
Здравствуйте, AlexGin, Вы писали:
N>>В чём эта "практика"? Если вы учитываете, какими порциями смог прочитать recv() на приёмнике — вы уже успешно выстрелили себе в ногу. Не надо так.
AG>Ну хорошо — я накопил данные от первого recv(), затем следующего и т.д — пока не уяснил что блок_пользовательских_данных принят весь. AG>Блок_пользовательских_данных — это тот полный набор данных, что следует от сервера к клиенту. AG>Таким образом, если при передаче по сети его разбило на несколько частей, то я их могу "накопить" и соединить вместе. AG>Так нормально?
Да, так нормально.
Теперь надо решить, как определять, что передача от сервера закончилась (а для сервера — аналогично от клиента). Есть разные стили фрейминга для этого. Например, один из классических — вначале идёт длина всей посылки в двоичном виде (например, 4 байта в network order == big-endian). Но можно придумать тысячи других по вкусу.
Здравствуйте, Pzz, Вы писали:
vsb>>Где?
Pzz>Вторая — Тут
Не так прочитал, думал, ты про то, что IP пакет защищает тело контрольной суммой. В общем я к тому, что в TCP одна контрольная сумма, а не несколько, поэтому она и вправду не стопроцентно надёжная. Насчёт транспортного протокола не знаю, с ними не сталкивался.
Здравствуйте, netch80, Вы писали:
N>Теперь надо решить, как определять, что передача от сервера закончилась (а для сервера — аналогично от клиента). Есть разные стили фрейминга для этого. Например, один из классических — вначале идёт длина всей посылки в двоичном виде (например, 4 байта в network order == big-endian). Но можно придумать тысячи других по вкусу.
Главное не забыть, что длина тоже может прийти кусками И хорошо бы её проверять, прежде чем выделять буфер указанного размера, а то придёт 4 миллиарда от шутника какого-нибудь. 4 гигабайта пока ещё ощутимый объём памяти.
Здравствуйте, vsb, Вы писали:
vsb>Не так прочитал, думал, ты про то, что IP пакет защищает тело контрольной суммой. В общем я к тому, что в TCP одна контрольная сумма, а не несколько, поэтому она и вправду не стопроцентно надёжная. Насчёт транспортного протокола не знаю, с ними не сталкивался.
Ну, она CRC32. В массштабах 4-гигабайтного кино вероятность пропустить одиночную ошибку больше половины. С другой стороны, кто его качает, кино, по голому TCP?
Здравствуйте, Pzz, Вы писали:
AG>>>Ну или же каждый разработчик прикладного ПО на_верхнем_уровне защищается от ситуации по-своему.
M>>Никто при использовании TCP в здравом уме не защищается
Pzz>Ну строго говоря, у TCP очень паршивая контрольная сумма. Если гнать большие объемы данных по шумному каналу, данные иногда будут приходить попорченными. Но ТС вряд ли именно от этого защищается.
Могу предположить, что у такого "шумного канала" есть свои механизмы контроля целостности данных, которые расположены ниже уровня IP. В противном случае, имхо, такой канал никому не нужен будет.
Так, у Ethernet'а, например, есть ещё CRC32 кадра
Здравствуйте, уважаемый vsb, Вы писали:
vsb>Главное не забыть, что длина тоже может прийти кусками И хорошо бы её проверять, прежде чем выделять буфер указанного размера, а то придёт 4 миллиарда от шутника какого-нибудь. 4 гигабайта пока ещё ощутимый объём памяти.
1) Система работает в специальной сети (не общего пользования), так что шутники — курят в сторонке.
2) Под длину блока — выделяется четыре первых байта принятого массива — какой тебе кусок на менее, чем четыре байта
Здравствуйте, AlexGin, Вы писали:
vsb>>Главное не забыть, что длина тоже может прийти кусками И хорошо бы её проверять, прежде чем выделять буфер указанного размера, а то придёт 4 миллиарда от шутника какого-нибудь. 4 гигабайта пока ещё ощутимый объём памяти. AG> AG>1) Система работает в специальной сети (не общего пользования), так что шутники — курят в сторонке. AG>2) Под длину блока — выделяется четыре первых байта принятого массива — какой тебе кусок на менее, чем четыре байта
Имеется в виду вариант типа такого:
вызвали recv() — пришло, например, 1460 байт (типовой MSS на Ethernet без timestamp-опций).
Из них 1458 — первое сообщение с заголовком длины. А ещё 2 — начало заголовка длины второго сообщения.
Теперь надо эти 2 сохранить, ответ следующего recv() дописать к ним и уже тогда декодировать длину.