Daniel Ginsburg ([info]dbg) wrote,
@ 2007-10-25 14:05:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Иногда я захожу почитать rsdn.ru. Из чистого любопытства. Иногда там кроме "жабобыдлокодерства" обсуждается что-нибудь интересное. Сегодня я натолкнулся на тред про TCP.

Вопрос формулируется так: Программы обмениваются сообщениями через TCP. Сообщения короткие, с десяток байт. Возможно ли такое что на принимающей стороне recv вернёт меньшее количество байт чем размер сообщения?

Обсуждение не столько интересное, сколько показательное. Правильные ответы там, естественно, прозвучали, однако завязалась мутная дискуссия, в которой много чего перепуталось. Иными словами, в широких программистских массах есть определенное недопонимание того, что происходит и почему. Видимо, надо раскрыть тему для тех, кто не дочитал Стивенса.

Ничего нового я не расскажу, все это многократно описано.

Казалось бы, все давно усвоили, что TCP - byte stream протокол и полагаться на сохранение границы сообщения нельзя, однако все равно у людей возникает соблазн думать: "ну, мы будем посылать маленькие сообщения, nagle отключим, тогда сообщения будут приходить по одному на TCP сегмент и все будет круто". Так вот - круто не будет.

Во-первых, как обычно, в том треде первым делом перепутали гарантированный IP reassembly buffer размером 576 октетов и minimum IP MTU, который на самом деле составляет 68 октетов (RFC 791). Впрочем, это уже хорошая традиция, путать эти вещи. Соответственно, при использовании PMTUD те самые "маленькие сообщения" могут стать недостаточно маленькими и начать сегментироваться. Но это теоретическая опасность - я не могу припомнить ни одной ситуации, где бы я видел такой MTU вне лаборатории.

Во-вторых, всегда есть возможность, что в сети появится какой-нибудь application level proxy, какой-нибудь socks или что-нибудь из этой оперы, который TCP поток примет, пересоберет как ему понравилось и отправит дальше. Опасность вполне реальная, но оптимистами от сетевого программирования часто игнорируется.

А могут ли возникнуть неприятности в "нормальных условиях"? Т.е. в ситуации с реалистичным MTU, без каких-либо хитрых прокси и т.д. Да, могут. И воспроизвести такую ситуацию достаточно несложно.

Напишем пару простеньких програмулек: клиент и сервер. Клиент посылает по 17 байт с TCP_NODELAY, а сервер в свою очередь, читает по 17 байт. Оба печатают чего и сколько они послали или получили. Код приводить не буду - все тривиально.

Запускаем програмульки и смотрим tcpdump'ом:
13:38:00.067070 IP 172.16.13.1.1086 > 172.16.13.2.10000: S 854406314:854406314(0) win 5840 <mss 1460,nop,wscale 0>
13:38:00.067144 IP 172.16.13.2.10000 > 172.16.13.1.1086: S 734559824:734559824(0) ack 854406315 win 5840 <mss 1460,nop,wscale 0>
13:38:00.067322 IP 172.16.13.1.1086 > 172.16.13.2.10000: . ack 1 win 5840
13:38:00.067832 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 1:18(17) ack 1 win 5840
13:38:00.067859 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 18 win 5840
13:38:00.072556 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 18:35(17) ack 1 win 5840
13:38:00.072556 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 35 win 5840
13:38:00.080546 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 35:52(17) ack 1 win 5840
13:38:00.080798 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 52 win 5840
13:38:00.090530 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 52:69(17) ack 1 win 5840
13:38:00.090577 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 69 win 5840
13:38:00.101722 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 69:86(17) ack 1 win 5840
13:38:00.101752 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 86 win 5840
13:38:00.102886 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 86:103(17) ack 1 win 5840
13:38:00.123053 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 103:120(17) ack 1 win 5840
13:38:00.139038 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 120 win 5840
13:38:00.139301 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 120:137(17) ack 1 win 5840
13:38:00.141417 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 137:154(17) ack 1 win 5840
13:38:00.153849 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 154:171(17) ack 1 win 5840
13:38:00.189821 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 171 win 5840
Все честно. MSS нормальный, окошки хорошие, пакетики уходят с payload'ом по 17 байт.
Соответсвенно отправитель печатает "send 17 bytes", а приемник - "recv 17 bytes" много-много раз. Пока все нормально.

Теперь пусть отправитель шлет как можно быстрее, а приемник у нас будет помедленней и между чтениями спит по 50 миллисекунд. Ничего нереалистичного в этом нет. Приемник может жить на медленной машине, заниматься какой-то обработкой и т.п.

Естественно, при этом будет расти буфер сначала на приемнике:
dg@server:~$ netstat -ant | grep 10000
tcp        0      0 0.0.0.0:10000           0.0.0.0:*               LISTEN
tcp    80045      0 172.16.13.2:10000       172.16.13.1:1086        ESTABLISHED

Потом приемник окошко прикроет:
13:38:57.500247 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 96944 win 0

Буфер начнет расти на отправителе:
dg@client:~$ netstat -ant | grep 10000
tcp        0  13540 172.16.13.1:1086        172.16.13.2:10000       ESTABLISHED

Что показывает tcpdump:
13:39:01.043581 IP 172.16.13.1.1086 > 172.16.13.2.10000: . ack 1 win 5840
13:39:01.043611 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 96944 win 0
13:39:04.929485 IP 172.16.13.1.1086 > 172.16.13.2.10000: . ack 1 win 5840
13:39:04.929746 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 96944 win 0
13:39:10.449838 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 96944 win 2144
13:39:10.450307 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 96944:97344(400) ack 1 win 5840
13:39:10.450310 IP 172.16.13.1.1086 > 172.16.13.2.10000: . 97344:98804(1460) ack 1 win 5840
13:39:10.450350 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 97344 win 1744
13:39:10.549359 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 98804 win 284
13:39:10.773453 IP 172.16.13.1.1086 > 172.16.13.2.10000: P 98804:99088(284) ack 1 win 5840
13:39:10.969739 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 99088 win 0
13:39:11.252583 IP 172.16.13.1.1086 > 172.16.13.2.10000: . ack 1 win 5840
13:39:11.253306 IP 172.16.13.2.10000 > 172.16.13.1.1086: . ack 99088 win 0

Zero window probes и silly window avoidance во всей красе. А отправитель, совершенно естественно, шлет уже не по 17 байт, а кусками побольше.

А теперь устроим небольшую аварию. Типа у нас временно сломалась сеть. Бывает такое? Да конечно бывает, что за вопрос:
dg@client:~$ sudo iptables -A OUTPUT --dst 172.16.13.2 -j DROP

Теперь приемник ничего не получает, а только разгребает свой буфер по 17 байт раз в 50мс. Разгребает, разгребает и ...:
...
recv 17 bytes
recv 17 bytes
recv 1 bytes

Вот, собственно, и оно.

Теперь, если "аварию" устранить, то поток восстановится, и получатель опять сможет читать свои 17 байт. Но уже читать он будет полную херню, ибо весь поток сместился.

Итого, ответ на вопрос Возможно ли такое что на принимающей стороне recv вернёт меньшее количество байт чем размер сообщения? будет "Да, конечно, безусловно, непременно. А кто этого не понимает, будет бит по голове утяжеленным томом Стивенса."



(Post a new comment)


[info]alexott
2007-10-25 10:29 am UTC (link)
подарочным изданием из 3-х томов :-)

(Reply to this) (Thread)


[info]dbg
2007-10-25 10:41 am UTC (link)
Если уж так, то тогда полным комплектом: APUE, оба тома UNP и три TCP/IP Illustrated. Но есть опасность, что обучаемый не выживет :)

(Reply to this) (Parent)(Thread)


[info]awind
2007-10-25 10:47 am UTC (link)
остальными продублировать по шаловливым ручкам ;)

(Reply to this) (Parent)


[info]alexott
2007-10-25 10:52 am UTC (link)
кстати - да :-)

(Reply to this) (Parent)


[info]1esha
2007-10-25 10:45 am UTC (link)
а для идиотов объяснить почему так произошло можно? :)

(Reply to this) (Thread)


[info]dbg
2007-10-25 10:49 am UTC (link)
Эээ. Я даже затрудняюсь. Мне казалось, я написал со всеми подробностями. Если непонятно на каком-то шаге, то я объясню, надо только сказать на каком.

(Reply to this) (Parent)(Thread)


[info]yushkin
2007-10-25 01:47 pm UTC (link)
recv 1 bytes - получатель ожидает 17 байт, соответственно этот байтик он не получит или все-таки ПО получит 1 байт с указанием, что это 1 байт, а не 17?

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

(Reply to this) (Parent)(Thread)

(no subject) - [info]dbg, 2007-10-25 01:59 pm UTC
(no subject) - [info]yushkin, 2007-10-25 04:54 pm UTC
(no subject) - [info]dbg, 2007-10-25 05:22 pm UTC
(no subject) - [info]nealar, 2007-10-25 05:47 pm UTC
(no subject) - [info]dbg, 2007-10-25 05:49 pm UTC

[info]1esha
2007-10-26 02:27 am UTC (link)
я как раз представитель "жабокодеров", но интересно почему так происходит :)

> Потом приемник окошко прикроет
Это из за того что на приемнике заполнился буффер?

> Zero window probes и silly window
а можно про это поподробнее?

> А отправитель, совершенно естественно, шлет уже не по 17 байт, а кусками побольше.
почему? :)

> Разгребает, разгребает и ...:
здесь видимо из-за того что передатчик слал не по 17 байт а кусками побольше и в один прекрасный момент дошел пакет в котором был лишь кусок сообщения, в то время как его остаток остался в буффере отправителя?

(Reply to this) (Parent)(Thread)

(no subject) - [info]dbg, 2007-10-26 10:57 am UTC

[info]aadz
2007-10-25 04:03 pm UTC (link)
Я всех тонкостей тоже не понял, не сильно вдавался в аргументацию, но достаточно уже и того, что:

> TCP - byte stream протокол и полагаться на сохранение границы сообщения нельзя

Вот это и произошло. Просто не надо полагаться на то, чего TCP не гарантирует, и, мало того, гарантирует именно обратное.

(Reply to this) (Parent)


[info]dimrub
2007-10-25 10:49 am UTC (link)
Кстати, последнее переиздание Стивенсона раза в три тоньше предыдущих. Кол-во страниц - то же. Бумага там папиросная, что-ли?

(Reply to this) (Thread)


[info]dbg
2007-10-25 10:52 am UTC (link)
Он Стивенс, Стивенсон - это автор "Острова сокровищ" ;)

А последнего издания я не видел. Если там папиросная бумага, то будем бить старым, которое было на правильной бумаге с правильным переплетом :)

(Reply to this) (Parent)(Thread)

(no subject) - [info]alexott, 2007-10-25 10:52 am UTC
(no subject) - [info]dimrub, 2007-10-25 10:58 am UTC

[info]visir
2007-10-25 11:00 am UTC (link)
С сообщениями побольше (256 байт в частности, а может и 512, не помню точно) в свежем линуксе другая приколюха: он начинает с меньшего окна, и сообщение уходит несколькими пакетами...

PS: недавно вышла новая редакция APUE, и ее уже успели перевести )

(Reply to this) (Thread)


[info]dbg
2007-10-25 11:02 am UTC (link)
tcpdump покажешь?

(Reply to this) (Parent)(Thread)

(no subject) - [info]visir, 2007-10-25 11:03 am UTC
(no subject) - [info]visir, 2007-10-25 04:05 pm UTC
(no subject) - [info]dbg, 2007-10-25 04:08 pm UTC
(no subject) - [info]visir, 2007-10-25 05:05 pm UTC
(no subject) - [info]dbg, 2007-10-25 05:26 pm UTC
(no subject) - [info]visir, 2007-10-25 05:31 pm UTC
(no subject) - [info]dbg, 2007-10-25 05:41 pm UTC
(no subject) - [info]visir, 2007-10-25 05:54 pm UTC
(no subject) - [info]visir, 2007-10-25 05:32 pm UTC

[info]reedcat
2007-10-25 11:37 am UTC (link)
Увы... Никто никого не ударит... :(
Ибо методика написания кривого софта, хараткерная для "троечников" уже стала стандартом де-факто. :(


P.S. А за рассказ - спасибо! В тебе погибает гениальный методист. :)

(Reply to this) (Thread)


[info]dbg
2007-10-25 11:54 am UTC (link)
На здоровье :) Спасибо за комплимент.

Я немножко оптимист. Наверное, кому-то еще можно помочь. Поэтому рассказывать, писать, объяснять надо.

(Reply to this) (Parent)(Thread)

(no subject) - [info]reedcat, 2007-10-25 02:08 pm UTC
(no subject) - [info]dbg, 2007-10-25 02:24 pm UTC
(no subject) - [info]1esha, 2007-10-26 05:57 am UTC

[info]nealar
2007-10-25 12:12 pm UTC (link)
А я так писал. И работало. Потом нашёл время и вкрутил reassembly buffer. Так и не успел наткнуться на описываемое.

(Reply to this) (Thread)


[info]dbg
2007-10-25 12:19 pm UTC (link)
"Нашла чем хвастаться" ;)

Ну, ты сам все понимаешь. Можно и мультредные программы писать без синхронизации, и оно даже может "работать". А везти может до только до некоторых пор. Я ведь описал не какой-то угловой случай, а совершенно обычные сетевые условия. Я надеюсь, ты больше не будешь так делать :)

(Reply to this) (Parent)(Thread)

(no subject) - (Anonymous), 2007-10-25 01:08 pm UTC

[info]evnp
2007-10-25 01:43 pm UTC (link)
Требую продолжения банкета! Можно теперь ссылку на паттерн, как бороться с такой проблемой? В APUE он имеется? Постараюсь таки купить и прочесть.

Да, и ссылку на rsdn с правильными ответами можно?

В свое оправдание хочу сказать, что писал на Java и ниже XML-RPC/RMI не спускался, а тут внезапно пришлось ... надеюсь, ненадолго.

(Reply to this) (Thread)


[info]dbg
2007-10-25 01:51 pm UTC (link)
> Требую продолжения банкета!

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

> Можно теперь ссылку на паттерн, как бороться с такой проблемой?

Да все же просто. Надо проверять сколько прочиталось, сравнивать с тем, что ожидалось, и, если не хватило, дочитывать.

> В APUE он имеется?

Должен быть. По крайней мере в применении к unix pipes, точно.
Хотя APUE я в последний раз открывал лет семь назад. Так что память меня может и подводить.

> Да, и ссылку на rsdn с правильными ответами можно?

Если есть желаение, можете запостить.

> В свое оправдание хочу сказать

Да чего там оправдываться. Если раньше не знали, то теперь знаете. Значит все хорошо.

(Reply to this) (Parent)(Thread)

(no subject) - [info]captain_hell, 2007-10-25 02:39 pm UTC
(no subject) - [info]dbg, 2007-10-25 02:59 pm UTC
(no subject) - [info]captain_hell, 2007-10-25 08:13 pm UTC
(no subject) - (Anonymous), 2007-10-26 06:04 am UTC
(no subject) - [info]nuclight, 2008-10-30 11:18 am UTC

[info]_adept_
2007-10-25 07:37 pm UTC (link)
Все это я уже где-то когда-то читал, но там не было таких красивых примеров с tcpdump, и все прочитанное вылетело у меня из головы.

А теперь - с велосипедом примерами - у меня, можно сказать, новая жизнь начинается :)

Спасибо!

(Reply to this) (Thread)


[info]dbg
2007-10-26 09:21 am UTC (link)
Это тебе спасибо. Ты меня вдохновляешь своими байками. У меня получается скучнее и по-другому, но если б ты не писал своих рассказов, я бы, наверное, вел сплошной лытдбренный дневник.

(Reply to this) (Parent)


[info]romik_g
2007-10-25 07:52 pm UTC (link)
Даже я что-то понял. Интересно, познавательно, даже не смотря на то, что программированием я не занимался никогда... ну... скрипты иногда пишу - это да, но это ж чуть-чуть и не считается.
Спасибо, [info]dbg!!!

Ах, да. Дети потом будут по твоему ЖЖ учиться. ;)

(Reply to this) (Thread)


[info]dbg
2007-10-26 09:18 am UTC (link)
> Дети потом будут по твоему ЖЖ учиться.

Тааак. Надо завязывать с выпивкой, а то дети плохому научатся.

(Reply to this) (Parent)(Thread)

(no subject) - [info]romik_g, 2007-10-27 07:16 pm UTC

[info]migmit
2007-10-31 12:42 pm UTC (link)
Классно, спасибо.
Всегда проверял результат recv, но не формулировал, что может накрыться.

(Reply to this)


[info]nuclight
2008-10-30 10:58 am UTC (link)
Сюда бы еще демонстрацию, почему нет гарантированной доставки, того что ушло в send(), и почему нужен apllication-level ACK - тоже распространенная ошибка. Настолько, что ее сделали даже дизайнеры XMPP/Jabber: http://www.jabber.ru/bugzilla/show_bug.cgi?id=275

(Reply to this)


Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…