![]() Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
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, и если да, то с клиента считывается строка и отдается ему назад.
|