Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






IP-фрагментация






Как мы отмечали, канальный уровень сети обычно налагает ограни­чение на максимальный размер передаваемого кадра. Каждый раз, когда IP-модуль отсылает дейтаграмму, он в процессе маршрутизации определяет, с которого интер­фейса она будет выслана, и запрашивает значениеMTU у этого интерфейса. Далее IP-модуль сравнивает MTLJ с размером дейтаграммы и, если последняя превышает максимальную единицу передачи, разбивает дейтаграмму на фрагменты. Фрагмен­тация может потребоваться как на машине-источнике исходной IP-дейтаграммы, так и на каких-либо промежуточных маршрутизаторах.

Если IP-дейтаграмма подверглась фрагментации, она не будет собрана, пока не достигнет конечного пункта назначения. (Этим сборка фрагментов в IP-сетях прин­ципиально отличается от принятой в некоторых других сетях поэтапной схемы, ког­да фрагменты собираются на ближайшем пункте пересылки.) Фрагментация, по идее, не касается транспортного уровня и происходит прозрачным для него образом, так что если не пытаться предотвратить сниже? гае производительности, сопровожда­ющее фрагментацию, то ее можно и вовсе не учитывать на уровне TCP и UDP. Фраг­мент дейтаграммы, в свою очередь, может быть фрагментирован при перемещении по трассе. Информация, содержащаяся в IP-заголовке, позволяет осуществлять мно­гократную фрагментацию.

Вспомним формат IP-заголовка и посмотрим, какие его поля отвечают за фрагментацию. Поле идентификатор (identification) содержит уникальный номер для каждой посылаемой IP-дейтаграммы. Это значение копируется в каждый фраг­мент, идентифицируя его принадлежность данной дейтаграмме (теперь становится понятным назначение этого поля — оно используется при сборке фрагментов). Один бит в поле флаги (flags) используется как индикатор продолжение следует (тоге fragments) и говорит о наличии следующих фрагментов. Этот флаг устанавливается в каждом фрагменте, на которые разбивается дейтаграмма, за исключением послед­него. Поле смещение фрагмента (fragment offset) содержит величину смещения фрагмента от начала исходной дейтаграммы. Кроме того, после фрагментации дей­таграммы поле общая длина (total length) в заголовке каждого фрагмента будет содер­жать размер данного фрагмента.

Другой бит поля флаги называется запрет фрагментации (don't fragment, DF). Если установлен флаг DF, модуль IP не станет фрагментировать дейтаграмму. Вмес­то этого дейтаграмма отбрасывается и по протоколу ICMP генерируется сообщение об ошибке необходима фрагментация, но установлен флаг запрета фрагментации, которое посылается отправителю пакета. (В следующем параграфе мы рас­смотрим случай генерации такого сообщения.)

Если IP-дейтаграмма была фрагментирована, то каждый фрагмент становится отдельным пакетом со своим собственным IP-заголовком. Такие пакеты маршрути­зируются независимо, и, как следствие, фрагменты дейтаграммы могут приходить в точку назначения с нарушением их очередности. Однако в IP-заголовках фрагмен­тов содержится вся необходимая информации для их правильной сборки в конечном пункте назначения.

Несмотря на то что IP-фрагментация выполняется " незаметно" для транспортно­го уровня, она может привести к нежелательным последствиям, которые сказывают­ся на уровнях выше IP. Дело в том, что из-за потери одного фрагмента потребуется передать повторно всю дейтаграмму, а поскольку в самом протоколе IP не предус­мотрены таймаут и повторная передача, то эти функции должны быть возложены на более высокие уровни. Протокол TCP осуществляет повторную передачу по таймауту, a UDP — нет. (Некоторые приложения, использующие UDP, сами реализуют ме­ханизм таймаута и ретрансмиссии.)

Если окажется, что потерян некоторый фрагмент сегмента TCP, то по таймауту будет повторена передача всего сегмента TCP, который содержался в фрагментированной IP-дейтаграмме. Повторная передача отдельного фрагмента IP-дейта­граммы невозможна в принципе. Действительно, если фрагментацию произвел не хост источника дейтаграммы, а один из промежуточных маршрутизаторов, то ис­точник не может знать, каким именно образом было выполнено разбиение на фраг­менты. Уже по одной этой причине желательно принимать меры для предотвра­щения фрагментации (в статье [Kent and Mogul, 1987] приводятся и другие аргументы).

Используя UDP, довольно просто спровоцировать фрагментацию. (Позднее мы увидим, что в протоколе TCP предусмотрены меры предотвращения фрагментации и приложение практически не может заставить TCP выслать сегмент такого размера, что потребуется его фрагментация.) С помощью нашей программы sock мы будем по­сылать UDP-пакеты возрастающего размера, чтобы в конце концов произошла фрагментация. В сети Ethernet с максимальной длиной кадра 1500 байтов для наших данных остается 1472 байта (20 байтов занимает IP-заголовок и 8 байтов -UDP-заголовок). Запустим программу sock четыре раза, задав размер посылаемых данных 1471, 1472, 1473 и 1474 байта (предвидим, что в двух последних случаях про­изойдет фрагментация):

 

bsdi % sock -u -i -nl –w1471 svr4 discard

bsdi % sock -u -i -nl –w1472 svr4 discard

bsdi % sock -u -i -nl –w1473 svr4 discard

bsdi % sock -u -i -nl –w1474 svr4 discard

 

На рис. 11.7 представлен обмен пакетами, отслеженный с помощью программы

tcpdump

 

10.0 bsdi.1112 > svr4.discard: udp 1471

2 21.008303 (21.0083) bsdi.1114 > svr4.discard: udp 1472

3 50.449704 (29.4414) bsdi.1116 > svr4.discard: udp 1473

(frag 26304: 148000+)

4 50.450040 (0.0003) bsdi > svr4: (frag 26304: 101480)

5 75.328650 (24.8786) bsdi.1118 > svr4.discard: udp 1474

(frag 26313: 1480@0+)

6 75.328982 (0.0003) bsdi > svr4: (frag 26313: 231480)

Рис. 11.7. Наблюдаемая фрагментация IP-дейтаграмм с данными UDP

 

 

Каждая из двух первых дейтаграмм (строки 1 и 2) умещается в кадр Ethernet и не фрагментируется. Длина же IP-дейтаграммы с 1473 байтами данных равна 1501, и она должна фрагментироваться (строки 3 и 4). Аналогично, дейтаграмма, содержа­щая 1474 байта, имеет длину 1502 байта и тоже фрагментируется (строки 5 и 6).

Когда IP-дейтаграмма фрагментируется, tcpdump выводит полезную дополни­тельную информацию в скобках в конце строки. Здесь frag 26304 (строки 3 и 4) и frag 26313 (строки 5 и 6) показьгвают значения поля идентификатор из IP-заго­ловка.

Следующее число в строке 3 в скобках между двоеточием и знаком @ указывает размер фрагмента без учета IP-заголовка. Первые фрагменты обеих дейтаграмм со­держат по 1480 байтов данных: 8 байтов UDP-заголовка и 1472 байта UDP-данных. (Если прибавить 20 байтов заголовка IP, получим пакет размером ровно 1500 бай­тов.) Второй фрагмент третьей дейтаграммы (строка 4) содержит всего один, после­дний байт данных. Второй фрагмент последней дейтаграммы (строка б) содержит последние два байта данных.

Одно из правил фрагментациитребует, чтобы размер области данных (сюда отно­сится все, что следует за IP-заголовком) был кратен 8 байтам для всех фрагментов, кроме, может быть, последнего. В нашем случае число 1480 кратно 8.

Параметр, следующий за знаком @ (at), соответствует смещению данных в фраг­менте относительно начала исходной дейтаграммы. Первые фрагменты обеих исход­ных дейтаграмм начинаются с нулевого байта (строки 3 и 5), а вторые фрагменты смещены на 1480 (строки 4 и 6). Символ " плюс" после значения смещения в первых фрагментах обеих дейтаграмм означает, что за данным фрагментом следуют другие, то есть, установлен флаг, продолжение следует в поле флагов IP-заголовка. Отсут­ствие этого флага сигнализирует приемнику, что на данном фрагменте сборка дей­таграммы заканчивается.

Наконец, отметим, что строки 4 и 6 (не первые фрагменты) не содержат информа­ции о протоколе (UDP) и номерах портов источника и назначения. Название прото­кола можно было бы установить, поскольку он указан в IP-заголовке каждого фраг­мента. Значения номеров портов, однако, содержатся в UDP-заголовке, который был передан только в первом фрагменте. На примере третьей из посланных дейтаграмм (содержащей 1473 байта данных) еще раз наглядно показано, как разби­вается UDP-пакет при фрагментации IP-дейтаграммы. Видим, что заголовок транс­портного уровня встречается лишь в первом фрагменте.

Обратим внимание на одну терминологическую тонкость: IP-дейтаграмма яв­ляется единицей обмена информацией на IP-уровне между оконечными точками (до фрагментации и после сборки). IP-пакетом называют порцию данных между IP-уровнем и канальным уровнем одной машины. IP-пакет может быть как полной IP-дейтаграммой, так и ее фрагментом.

 

Мультиплексирование ввода-вывода: функции select, poll

Код данной главы лежит тут

В предыдущей главе описывется клиент-сервер, у которого есть один существенный недостаток: во время чтения сокета канал блокируется до тех пор, пока клиент не получит все данные. Этот недостаток можно преодолеть, сообщив ядру, что мы хотим получить уведомление о том, что появились данные, которые можно считывать из сокета. Эта возможность называется мультиплексированием ввода-вывода и обеспечивается функциями select и poll.

Мультиплексирование используется обычно в следующих случаях:

1 Когда клиент обрабатывает множество дескрипторов 2 Сервер работает и с udp, и с TCP 3 Сервер обрабатывает множество служб

В UNIX вообще говоря доступны 5 основных моделей ввода-вывода:

1 Блокируемый 2 Неблокируемый 3 Мультиплексирование 4 Ввод-вывод, управляемый сигналом (SIGIO) 5 Асинхронный ввод-вывод

В операции ввода обычно 2 фазы:

1 Ожидание готовности данных - по приходу пакета он обычно копируется в буфер ядра 2 Копирование данных от ядра процессу - копирование из буфера ядра в буфер приложения

Наиболее распространенной моделью является блокируемый ввод-вывод. Все сокеты по умолчанию блокируемые.

Системная функция recfrom работает в режиме приложения, затем переключается в режим ядра, а затем возвращается в режим приложения. Процесс блокирован до тех пор, пока " ушедшая" в ядро функция не вернет данные.

Режим неблокируемого ввода-вывода отличается тем, что recvfrom вызывается каждый раз в цикле до тех пор, пока ядро не вернет данные для считывания:

Такой процесс называется опросом - polling. Вообще говоря, это пустая трата процессорного времени, но тем не менее.

Режим мультиплексированного ввода-вывода делает блокировку, но она происходит не при вводе-выводе, а при вызове select или poll.

Преимущество мультиплексированного ввода-вывода перед блокируемым в том, что мы можем обрабатывать не один, а несколько дескрипторов.

Модель ввода-вывода, управляемого сигналом, с помощью ядра посылает процессу сигнал SIGIO о готовности дескриптора.

Сигнал обрабатывается с помощью sigaction.

Сравнение моделей ввода-вывода:

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

int select (int maxfdp1, fd_set * readset, fd_set * writeset, fd_set * exeptset, const struct timeval * timeout) возвращает: либо положительное число готовых дескрипторов либо ноль в случае тайм-аута либо -1 в случае ошибки

С помощью последнего аргумента - времени - можно реализовать 3 сценария:

1 ждать вечно - timeval = NULL 2 ждать в течение определенного времени - в миллисекундах 3 не ждать вообще timeval = 0

Три средних аргумента определяют тип дескрипторов. Это изменяемый тип аргументов, и их значение можно проверить с помощью макроса FD_ISSET. Первый аргумент задает число проверяемых дескрипторов. Максимально возможное число дескрипторов обычно 1024.

А теперь начнем переписывать эхо-сервер, который был описан в предыдущей пятой главе. Перепишем его с помощью функции select и без использования fork. Сервер будет обслуживать набор дескрипторов для чтения. Дескрипторы 0, 1 и 2 будут соответственно потоками ввода, вывода и ошибок. Поэтому первым доступным для прослушивания сокетом станет номер 3, и первым аргументом функции select будет 4. Все дескрипторы будут содержаться в массиве client и проинициализированы как -1.

После того, как первый клиент установит коннект с сервером, картина станет такой:

Затем еще один слиент:

Затем первый клиент завершает соединение:

Код сервера:

//tcpcliserv/tcpservselect01.c int main(int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, client[FD_SETSIZE]; ssize_t n; fd_set rset, allset; char line[MAXLINE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(& servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA *) & servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); maxfd = listenfd; /* initialize */ maxi = -1; /* index into client[] array */ for (i = 0; i < FD_SETSIZE; i++) client[i] = -1; /* -1 indicates available entry */ FD_ZERO(& allset); FD_SET(listenfd, & allset); for (;;) { rset = allset; /* structure assignment */ nready = Select(maxfd+1, & rset, NULL, NULL, NULL); if (FD_ISSET(listenfd, & rset)) { /* new client connection */ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) & cliaddr, & clilen); #ifdef NOTDEF printf(" new client: %s, port %d\n", Inet_ntop(AF_INET, & cliaddr.sin_addr, 4, NULL), ntohs(cliaddr.sin_port)); #endif for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) { client[i] = connfd; /* save descriptor */ break; } if (i == FD_SETSIZE) err_quit(" too many clients"); FD_SET(connfd, & allset); /* add new descriptor to set */ if (connfd > maxfd) maxfd = connfd; /* for select */ if (i > maxi) maxi = i; /* max index in client[] array */ if (--nready < = 0) continue; /* no more readable descriptors */ } for (i = 0; i < = maxi; i++) { /* check all clients for data */ if ((sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, & rset)) { if ((n = Readline(sockfd, line, MAXLINE)) == 0) { /*4connection closed by client */ Close(sockfd); FD_CLR(sockfd, & allset); client[i] = -1; } else Writen(sockfd, line, n); if (--nready < = 0) break; /* no more readable descriptors */ } } } }

Функция select ждет, пока не будет установлено новое клиентское соединение, либо на существующем соединении не прибудут данные, сегмент FIN или сегмент RST.

Затем вызываем accept и меняем структуры данных. В каждом клиентском соединении проверяем, содержится ли его дескриптор в наборе дескрипторов client, и если да, то с клиента считывается строка и отдается ему назад.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.