Могут ли TCP-пакеты приходить к получателю по частям?
Например, если я отправлю 20 байтов по протоколу TCP, могу ли я быть на 100% уверен, что получу ровно 20 байтов за один раз, а не 10 байтов, а затем еще 10 байтов или около того?
И тот же вопрос по протоколу UDP.
Я знаю, что UDP ненадежен, и пакеты не могут приходить вообще или поступают в другом порядке, но как насчет одного пакета? Если он придет, могу ли я быть уверен, что это полный пакет, а не кусок?
может TCP-пакеты поступают получателю по частям?
Да. IP поддерживает фрагментацию, хотя TCP обычно пытается определить MTU пути и сохранять пакеты меньшего размера по соображениям производительности. Фрагментация катастрофически увеличивает скорость потери дейтаграмм. Если на пути коэффициент потери пакетов составляет 10%, то при фрагментировании дейтаграммы на два пакета коэффициент потери дейтаграмм становится почти 20%. (Если какой-либо пакет потерян, дейтаграмма потеряна.)
Однако вам не нужно беспокоиться об этом, и уровень TCP тоже. Уровень IP повторно собирает пакеты в целые дейтаграммы.
Например: если я отправлю 20 байтов по протоколу TCP, могу ли я быть на 100% уверен, что получу ровно 20 байтов за один раз, а не 10 байтов, а затем еще 10 байтов или около того?
Нет, но это не имеет отношения к пакетам. TCP - это, по сути, протокол потока байтов, который не сохраняет границы сообщений приложения.
И тот же вопрос по протоколу UDP. Я знаю, что UDP ненадежен, и пакеты не могут приходить вообще или приходят в другом порядке,
То же самое и с TCP. Пакеты есть пакеты. Разница в том, что TCP имеет встроенные в протокол повторные попытки и переупорядочивание, а UDP - нет.
а как насчет 1 пакета? Если он придет, могу ли я быть уверен, что это полный пакет, а не кусок?
Нет, но это не твоя проблема. Протокол UDP обрабатывает повторную сборку дейтаграммы. Это часть его работы. (На самом деле IP-протокол делает это для протокола UDP, поэтому UDP делает это просто путем наложения слоев поверх IP.) Если дейтаграмма разделена на два пакета, IP-протокол повторно соберет ее для протокола UDP, поэтому вы увидит полные данные.
Вы не можете быть уверены, что они действительно физически прибудут сразу. Уровни канала передачи данных ниже TCP / UDP могут разделить ваш пакет, если захотят. Это трудно предсказать, особенно если вы отправляете данные через Интернет или любые сети, находящиеся вне вашего контроля.
Но неважно, поступают ли данные одним пакетом или несколькими пакетами на приемник. ОС должна абстрагироваться от конкатенации этих пакетов, поэтому для вашего приложения все равно будет выглядеть так, как будто все пришло сразу. Итак, если вы не являетесь хакером ядра, в большинстве случаев вам не нужно беспокоиться, если эти данные передаются в одном или нескольких пакетах.
Для UDP ОС также будет выполнять некоторую абстракцию, поэтому приложение, которое получает данные, не должно знать, в каком количестве пакетов данные были переданы. Но отличие от TCP в том, что нет никакой гарантии, что данные действительно будут доставлены. Также возможно, что данные разделяются на несколько пакетов, и некоторые из них приходят, а некоторые нет. В любом случае для принимающего приложения это просто поток данных, независимо от того, завершен он или нет.
Примеры. Блоки смежных символов соответствуют вызовам send ():
TCP:
Send: AA BBBB CCC DDDDDD E Recv: A ABB B BCC CDDD DDDE
Все отправленные данные принимаются по порядку, но не обязательно одними и теми же порциями.
UDP:
Send: AA BBBB CCC DDDDDD E Recv: CCC AA E
Данные не обязательно находятся в одном и том же порядке и не обязательно принимаются вообще, но сообщения сохраняются целиком.
Например: если я отправлю 20 байтов по протоколу TCP, могу ли я быть на 100% уверен, что получу ровно 20 байтов за один раз, а не 10 байтов, а затем еще 10 байтов или около того?
Нет, TCP - это потоковый протокол, он хранит данные в порядке, но не группирует их по сообщениям. С другой стороны, UDP ориентирован на сообщения, но ненадежен. SCTP имеет лучшее из обоих миров, но не может использоваться изначально, потому что NAT разрушает Интернет.
Есть некоторая уверенность в том, что если вы отправите 20 байтов в самом начале потока TCP, они не будут доставлены как две части по 10 байтов. Это связано с тем, что стек TCP не отправляет такие маленькие сегменты: существует минимальный размер MTU. Однако, если отправка происходит где-то в середине потока, все ставки отключены. Может случиться так, что стек вашего протокола займет 10 байт данных для заполнения сегмента и его отправки, а затем следующие десять байтов перейдут в другой сегмент.
Стек протоколов разбивает данные на куски и помещает их в очередь. Размеры фрагментов основаны на MTU пути. Если вы выполняете операцию отправки, а данные в очереди все еще ожидают обработки, стек протокола обычно просматривает сегмент, который находится в конце очереди, и проверяет, есть ли в этом сегменте место для добавления дополнительных данных. Помещение может быть размером всего один байт, поэтому даже двухбайтовую отправку можно разбить на две.
С другой стороны, сегментация данных означает, что могут быть частичные чтения. Операция приема потенциально может активироваться и получать данные, когда поступает всего один сегмент. В широко реализованном API сокетов вызов приема может запрашивать 20 байтов, но он может возвращать 10. Конечно, на нем может быть построен уровень буферизации, который будет блокироваться до тех пор, пока не будут получены 20 байтов или пока соединение не разорвется. В мире POSIX этот API может быть стандартными потоками ввода-вывода: вы можете fdopen
дескриптор сокета для получения FILE *
поток, и вы можете использовать fread
на нем, чтобы заполнить буфер таким образом, чтобы полный запрос удовлетворялся как можно большим количеством read
звонит как надо.
Дейтаграммы UDP формируют данные. Каждый вызов send генерирует дейтаграмму (но о закупоривании см. Ниже). Другая сторона получает полную дейтаграмму (и в API сокета она должна указать буфер, достаточно большой для ее хранения, иначе дейтаграмма будет усечена). Большие датаграммы фрагментируются из-за IP-фрагментации и повторно собираются прозрачно для приложений. Если какой-либо фрагмент отсутствует, вся дейтаграмма теряется; в этой ситуации невозможно прочитать частичные данные.
Существуют расширения интерфейса, позволяющие нескольким операциям указывать одну дейтаграмму. В Linux сокет можно «закупорить» (запретить отправку). Пока он закупорен, записанные данные собираются в единый блок. Затем, когда сокет «отклеен», можно отправить одну дейтаграмму.