Что из нижеперечисленного не является прототипом функции. Прототип функции. Для чего нужны функции в C

Таблица 4.3 показывает структуру протоколов технологии FDDI в проекции на эталонную модель OSI. Определены протоколы физического уровня и подуров­ня MAC канального уровня.

Таблица 4.3. Структура протоколов в технологии FDDI

Физический уровень разделен на два подуровня: независящий от среды под­уровень PHY (Physical Media Independent) и зависящий от среды подуровень PMD (Physical Media Dependent). Работу всех уровней контролирует протокол управления станцией SMT (Station Management). Подуровень PMD обеспечива­ет передачу данных от одной станции к другой по конкретной физической среде, а подуровень PHY выполняет кодирование и декодирование данных, циркули­рующих между подуровнем MAC и подуровнем PMD, а также обеспечивает тактирование информационных сигналов.

Применительно к технологии ATM физический уровень делится на два под­уровня: подуровень согласования с системой передачи (Transmission Convergen­ce, ТС) и подуровень физической среды (Physical Medium - РМ). Подуровень согласования с системой передачи выполняет упаковку ячеек, поступающих с верхнего уровня модели ATM, в передаваемые транспортные кадры. Например, если ячейки ATM передаются через канал ЕЗ (34 Мбит/с), они должны упако­вываться в поле данных кадра ЕЗ. В случае, когда ячейки передаются напрямую по физической линии без использования транспортных кадров, упаковка ячеек не требуется. На этом уровне выполняется также подсчет контрольной суммы и т. д. Подуровень физической среды регламентирует скорость передачи данных и отвечает за синхронизацию между передачей и приемом. В табл. 4.4 перечисле­ны подуровни физического уровня ATM.

Таблица 4.4. Подуровни физического уровня ATM

В настоящее время существуют три организации, определяющие физический уровень технологии ATM: ANSI, ITU/CCITT и Форум ATM.

Стандарт ANSI T1.624 определяет три спецификации физического уровня для одномодового оптоволоконного кабеля, основанные на технологии SONET: STS-1 (51.84 Мбит/с), STS-Зс (155.52 Мбит/с) и STS-12c (622.08 Мбит/с). Кроме того, этот стандарт определяет работу на скорости 44.736 Мбит/с (DS3) с использованием протокола PLCP (Physical Layer Convergence Protocol, прото­кол согласования с физическим уровнем) из стандарта IEEE 802.6.



Рекомендация 1.432 комитета ITU определяет две спецификации физиче­ского уровня, основанные на синхронной цифровой иерархии SDH; STM-1 (155.52 Мбит/с) и STM-4 (622.08 Мбит/с). Ввиду того, что уровни STM-1 и STM-4 соответствуют уровням STS-3d и STS-12c технологии SONET, взаимо­действие между ними организуется достаточно просто. Помимо того, комитет ITU стандартизировал дополнительные спецификации физического уровня: DS1 (1.544 Мбит/с), Е1 (2.048 Мбит/с), DS2 (6.312 Мбит/с), ЕЗ (34.368 Мбит/с), DS3 (44.736 Мбит/с) с использованием PLCP и Е4 (139.264 Мбит/с).

Форум ATM определил четыре спецификации физического уровня для тех­нологии ATM: DS3 (44.736 Мбит/с), 100 Мбит/с, 155 Мбит/с и 622 Мбит/с.

Канальный уровень обеспечивает надежную передачу данных через физический канал. Канальный уровень оперирует блоками данных, называемыми кадрами (frame). В локальных сетях используется разделяемая среда передачи. Основ­ным назначением канального уровня является прием кадра из сети и отправка его в сеть. При выполнении этой задачи канальный уровень осуществляет:

q физическую адресацию передаваемых сообщений;

q соблюдение правил использования физического канала;

q выявление неисправностей;

q управление потоками информации.

В технологии ATM канальному уровню модели OSI соответствует уровень ATM. Вместо прямой адресации по мере прохождения ячеек с информацией через коммутаторы ATM в заголовках ячеек происходит преобразование иден­тификаторов виртуальных путей и каналов. Добавляется также новая функция: мультиплексирование н демультиплексирование ячеек.

Для доступа к среде в локальных сетях используются два метода:

q метод случайного доступа;

q метод маркерного доступа.

Метод случайного доступа основан на том, что любая станция сети пытается получить доступ к каналу передачи в необходимый для нее момент времени. Если канал занят, станция повторяет попытки доступа до его освобождения. Примером реализации этого метода является технология Ethernet.

Метод маркерного доступа применяется в сетях Token Ring, ArcNet, FDDI и l00VG-AnyLan. Он основан на передаче от одной станции сети к другой маркера доступа. При получении маркера станция имеет право передать свою инфор­мацию.

Особенностью этих методов является то, что все станции участвуют в пере­даче на равных основаниях.

Технология ATM для доступа к среде передачи использует метод фиксиро­ванных слотов. Большинство реализаций транспортного механизма применяют транспортные кадры определенного размера, в которые упаковываются ячейки ATM.

Канальный уровень обеспечивает правильность передачи каждого кадра, до­бавляя к кадру его контрольную сумму. Получатель кадра проверяет достовер­ность полученных данных путем сравнения вычисленной и переданной с кадром контрольных сумм. Тем не менее, такая схема применяется не всегда. Например, в технологии ATM формирование поля проверки ошибок в заголовке ячейки на передающей стороне, а также обнаружение ошибок и их исправление на прием­ной стороне, реализованы на физическом уровне.

Функции канального уровня реализуются установленными в компьютерах сетевыми адаптерами и соответствующими драйверами, а также различным ком­муникационным оборудованием: мостами, коммутаторами, маршрутизаторами. Эти устройства должны:

q Формировать кадры. При этом происходит формирование заголовка и размещение данных, поступивших с более высокого уровня. Кадры могут быть информационными и служебными.

q Анализировать и обрабатывать кадры.

q Принимать кадры из сети и отправлять кадры в сеть. В технологии ATM на этом уровне формируется и удаляется заголовок ячейки.

IEEE (Institute of Electrical and Electronics Engineers, Институт электротех­ники и электроники) предложил другой, широко используемый, вариант модели OSI. IEEE-модель отличается тем, что в локальных сетях канальный уровень разделяется на два подуровня:

q Уровень управления логическим каналом (Logical Link Control - LLC);

q Уровень доступа к среде (Media Access Layer - MAC).

Уровень LLC отвечает за достоверную передачу кадров между станциями сети и взаимодействие с сетевым уровнем. МАС-уровень лежит ниже LLC-уровня и обеспечивает доступ к каналу передачи данных. Уровень LLC дает более высоким уровням возможность управлять качеством услуг. LLC обеспечивает сервис трех типов:

q Сервис без подтверждения доставки и установления соединения. Он не гарантирует доставку кадров. Этот вид сервиса называют дейтаграммным. Он чаще применяется в приложениях, использующих протоколы более высоких уровней, которые сами обеспечивают защиту от ошибок и под­держивают потоковую передачу данных;

q Сервис с установлением соединения, способный обеспечить надежный обмен кадрами;

q Сервис без установления соединения с подтверждением доставки.

Главной функцией МАС-уровня является обеспечение доступа к каналу. На этом уровне формируется физический адрес устройства, подсоединенного к ка­налу. Этот физический адрес также называется МАС-адресом. Каждое устройст­во сети идентифицируется этим уникальным адресом, который присваивается всем сетевым интерфейсам устройства. МАС-адрес позволяет выполнять точечную адресацию кадров, групповую и широковещательную. При передаче данных в сети отправитель указывает МАС-адрес получателя в передаваемом кадре.

Кроме того, МАС-уровень должен согласовывать дуплексный режим работы уровня LLC с физическим уровнем. Для этого он буферизует кадры для переда­чи их по назначению в момент получения доступа к среде.

Функции протоколов канального уровня различаются в зависимости от того, предназначен ли данный протокол для передачи информации в локальных или в глобальных сетях. Протоколы канального уровня в локальных сетях ориен­тируются на использование разделяемой между компьютерами среды передачи данных. Поэтому в этих протоколах имеется подуровень доступа к разделяемой среде. Хотя канальный уровень локальной сети и обеспечивает доставку кадра между любыми двумя узлами локальной сети, он делает это только в сети с совершенно определенной топологией связей, а именно, с той топологией, для которой он был разработан. К типовым топологиям, поддерживаемым протоко­лами канального уровня локальных сетей, относятся: общая шина, кольцо и звезда.

Использование разделяемой среды делает процедуры управления потоком кадров ненужными в локальных сетях. Локальная сеть базовой топологии не может переполниться кадрами, так как узлы сети не могут начать отправку но­вого кадра до приема предыдущего кадра станцией назначения.

Еще одной особенностью протоколов канального уровня локальных сетей яв­ляется широкое использование дейтаграммного метода доставки данных. Это объясняется хорошим качеством каналов связи, редко искажающим биты в пе­редаваемых кадрах.

Примерами протоколов канального уровня для локальных сетей являются Token Ring, Ethernet, Fast Ethernet, l00VG-AnyLAN, FDDI.

В глобальных сетях, которые редко обладают регулярной топологией, каналь­ный уровень обеспечивает обмен сообщениями между двумя соседними ком­пьютерами, соединенными отдельной линией связи. К таким протоколам типа «точка-точка» относятся РРР, SLIP, LAP-B и LAP-D. Эти протоколы не исполь­зуют подуровень доступа к среде, но требуют процедур управления потоком кадров, так как промежуточные коммутаторы могут переполняться при слишком высокой интенсивности трафика.

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

В технологии ATM соединение реализуется механизмом виртуальных кана­лов и виртуальных путей, регламентированных на уровне ATM. Каждое соеди­нение имеет свои идентификаторы виртуального канала и виртуального пути. При этом один виртуальный путь может состоять из нескольких виртуальных каналов при необходимости передачи трафика от различных пользователей.

Глава 2 Канальный уровень

Введение

Из рисунка 1.4 видно, что основная задача канального уровня в семействе протоколов TCP/IP - посылать и принимать (1) IP датаграммы для IP модуля, (2) ARP запросы и отклики для ARP модуля, и (3) RARP запросы и отклики для RARP модуля. TCP/IP поддерживает различные канальные уровни, в зависимости от того какой тип сетевого аппаратного обеспечения используется: Ethernet, Token ring, FDDI (Fiber Distributed Data Interface), последовательные линии RS-232, и так далее.

В этой главе мы подробно рассмотрим канальный уровень Ethernet, два специализированных канальных уровня для последовательных интерфейсов (SLIP и PPP) и драйвер loopback, который присутствует практически во всех реализациях. Ethernet и SLIP это канальные уровни, используемые для большинства примеров в данной книге. Также мы рассмотрим максимальный блок передачи (MTU - Maximum Transmission Unit), который является характеристикой канального уровня и к которой мы обращаемся много раз в этой главе и в следующих. Также мы покажем некоторые расчеты, с помощью которых можно выбрать MTU для последовательной линии.

Ethernet и IEEE 802 инкапсуляция

Термин Ethernet обычно означает стандарт, опубликованный в 1982 году компаниями Digital Equipment Corp., Intel Corp., и Xerox Corp. В настоящее время это основная технология применяемая в локальных сетях использующих TCP/IP. В Ethernet используется метод доступа, называемый CSMA/CD, что обозначает наличие несущей (Carrier Sense), множественный доступ (Multiple Access) с определением коллизий (Collision Detection). Обмен осуществляется со скоростью 10 Мбит/сек, с использованием 48-битных адресов.

Несколько лет спустя Комитет 802 Института инженеров по электротехнике и радиоэлектронике ( IEEE - Institute of Electrical and Electronics Engineers) опубликовал отличающийся набор стандартов. 802.3 описывает полный набор сетей CSMA/CD, 802.4 описывает сети с передачей маркера и 802.5 описывает сети Token ring. Общим для всех них является стандарт 802.2, который определяет управление логическим каналом ( LLC - Logical link control) и который является общим для большинства сетей 802. К сожалению, комбинация 802.2 и 802.3 определяет форматы фрейма отличные от Ethernet ( описывает все детали стандартов IEEE 802).

В мире TCP/IP инкапсуляция IP датаграмм определена в RFC 894 для сетей Ethernet и в RFC 1042 для сетей IEEE 802. В Host Requirements RFC к каждому компьютеру, подключенному к Internet через кабель Ethernet 10 Мбит/сек, предъявляются следующие требования:

  1. Компьютер должен иметь возможность посылать и получать пакеты, инкапсулированные с использованием RFC 894 (Ethernet).
  2. У компьютера должна быть возможность получать пакеты RFC 1042 (IEEE 802), перемешанные с пакетами RFC 894.
  3. Компьютер должен иметь возможность посылать пакеты с использованием инкапсуляции RFC 1042. Если компьютер может посылать оба типа пакетов, то тип пакета должен быть конфигурируемым, а конфигурация по умолчанию должна быть настроена на пакеты RFC 894.

Наиболее широко используется инкапсуляция RFC 894. На рисунке 2.1 показаны два различных метода инкапсуляции. Цифры под каждым квадратиком на рисунке это размер в байтах.

В обоих форматах фрейма используется 48-битовый (6-байтовый) формат представления адресов источника и назначения (802.3 позволяет использование 16-битных адресов, однако обычно используются 48-битные). Это как раз то, что мы называем по тексту аппаратными адресами (hardware addresses). Протоколы ARP и RARP (см. главу 4 и главу 5) устанавливают соответствие между 32-битными IP адресами и 48-битными аппаратными адресами.

Следующие 2 байта в этих форматах фрейма различаются. Поле длины (length) 802 содержит количество следующих за ним байтов, однако не содержит в конце контрольной суммы. Поле тип (type) в Ethernet определяет тип данных, которые следуют за ним. Во фрейме 802 то же поле типа (type) появляется позже в заголовке протокола доступа к подсети (SNAP - Sub-Network Access Protocol). К счастью, величины, находящиеся в поле длины (length) 802, никогда не совпадают с величинами, находящимися в поле типа (type) Ethernet, поэтому эти два формата фрейма легко различимы.

Во фрейме Ethernet данные следуют сразу после поля тип (type), тогда как во фрейме 802 за ним следуют 3 байта LLC 802.2 и 5 байт SNAP 802.2. Поля DSAP (точка доступа к сервису назначения - Destination Service Access Point) и SSAP (точка доступа к сервису источника - Source Service Access Point) оба установлены в 0xAA. Поле ctrl установлено в 3. Следующие 3 байта, org code установлены в 0. Затем идет 2-байтовое поле тип (type), такое же, как мы видели в формате фрейма Ethernet (дополнительные значения, которые могут появиться в поле типа, описаны в RFC 1340 ).

Поле контрольной суммы ( CRC) определяет ошибки, возникшие при транспортировке фрейма (также оно иногда называется FCS или последовательность контроля фрейма - frame check sequence).

Минимальный размер фреймов 802.3 и Ethernet требует, чтобы размер данных был хотя бы 38 байт для 802.3 или 46 байт для Ethernet. Чтобы удовлетворить этому требованию, иногда вставляются байты заполнения, для того чтобы фрейм был соответствующей длины.

Мы еще столкнемся с минимальным размером, когда будем рассматривать движение пакетов по кабелям. Также мы еще не раз обратимся к инкапсуляции Ethernet, потому что это, пожалуй, самая широко распространенная форма инкапсуляции.

Рисунок 2.1 Инкапсуляция IEEE 802.2/802.3 (RFC 1042) и инкапсуляция Ethernet (RFC 894).

Инкапсуляция завершителей

На рисунке 2.5 приведен список некоторых типичных значений MTU, взятых из RFC 1191 . Здесь приведены MTU для каналов точка-точка (таких как SLIP или PPP), однако они не являются физической характеристикой среды передачи. Это логическое ограничение, при соблюдении которого обеспечивается адекватное время отклика при диалоговом использовании. В разделе главы 2 мы рассмотрим, откуда берется это ограничение.

В разделе "Команда netstat" главы 3 мы воспользуемся командой netstat, чтобы определить MTU для определенного интерфейса.

Network

MTU (байты)

Hyperchannel
16 Мбит/сек Token ring (IBM)
4 Мбит/сек Token ring (IEEE 802.5)
FDDI
Ethernet
IEEE 802.3/802.2
X.25
Точка-точка (с маленькой задержкой)

Рисунок 2.5 Типичные значения максимальных блоков передачи (MTU).

Транспортный MTU

Когда общаются два компьютера в одной и той же сети, важным является MTU для этой сети. Однако, когда общаются два компьютера в разных сетях, каждый промежуточный канал может иметь различные MTU. В данном случае важным является не MTU двух сетей, к которым подключены компьютеры, а наименьший MTU любого канала данных, находящегося между двумя компьютерами. Он называется транспортным MTU (path MTU).

Транспортный MTU между любыми двумя хостами может быть не постоянным. MTU зависит от загруженности канала на настоящий момент. Также он зависит от маршрута. Маршрут может быть несимметричным (маршрут от A до B может быть совсем не тем, что маршрут от B к A), поэтому MTU может быть неодинаков для этих двух направлений.

RFC 1191 описывает механизм определения транспортного MTU (path MTU discovery mechanism). Мы рассмотрим как функционирует этот механизм после того, как опишем фрагментацию ICMP и IP. В разделе "ICMP ошибки о недоступности" главы 11 мы подробно рассмотрим ошибку недоступности ICMP, которая используется в этом механизме, а в разделе "Определение транспортного MTU с использованием Traceroute" главы 11 мы покажем версию программы traceroute, которая использует механизм определения транспортного MTU до пункта назначения. В разделах "Определение транспортного MTU при использовании UDP" главы 11 и "Определение транспортного MTU" главы 24 показано, как функционируют UDP и TCP, когда реализация поддерживает определение MTU.

Вычисление загруженности последовательной линии

Если скорость в линии составляет 9600 бит/сек, при этом 1 байт составляет 8 бит плюс 1 старт-бит и 1 стоп-бит, скорость линии будет 960 байт/сек. Передача пакета размером 1024 байта с этой скоростью займет 1066 мс. Если мы используем SLIP канал для диалогового приложения и одновременно с ним работает такое приложение как FTP, которое посылает или принимает пакеты по 1024 байт, мы должны ждать, так как среднее время задержки нашего интерактивного пакета составит 533 мс.

Это означает, что наш диалоговый пакет будет послан по каналу перед любым другим "большим" пакетом. Большинство SLIP приложений предоставляют разделение пакетов по типу сервиса, отправляя диалоговый трафик перед трафиком передачи данных. Диалоговый трафик это, как правило, Telnet, Rlogin и управляющая часть (пользовательские команды, но не данные) FTP.

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

Ожидание в 533 мс неприемлемо для диалогового ответа. С точки зрения человеческого фактора мы знаем, что неприемлемой является задержка дольше чем 100-200 мс [ Jacobson 1990a]. Под задержкой подразумевается время между отправкой пакета и возвращением отклика (как правило, эхо символа).

Уменьшение MTU в канале SLIP до 256 означает, что максимальное время, в течение которого канал может быть занят одним фреймом, составляет 266 мс, и половина от этого (наше среднее время ожидания) составляет 133 мс. Это лучше, однако до сих пор не идеально. Причина, по которой мы выбрали это значение (как сравниваются 64 и 128), заключается в том, чтобы обеспечить лучшее использование канала для передачи данных (как, например, при передаче большого файла). В случае CSLIP фрейма размером 261 байт с заголовком размером в 5 байт (256 байт данных), 98,1% линии используются для передачи данных и 1,9% на заголовки. Уменьшение MTU меньше чем 256 уменьшает максимальное значение пропускной способности линии, которую мы можем получить при передаче данных.

Значение MTU равное 296 для канала точка-точка (рисунок 2.5), подразумевает 256 байт данных и 40 байт TCP и IP заголовков. Так как MTU это величина, о которой IP узнает от канального уровня, это значение должно включать в себя стандартные заголовки TCP и IP. Именно таким образом IP принимает решение о фрагментации. IP ничего не знает о сжатии заголовков, которое осуществляются CSLIP.

Наш расчет средней задержки (половина того времени, которое требуется на передачу фрейма максимального размера) имеет отношение только к каналу SLIP (или каналу PPP), который используется для передачи интерактивного трафика и трафика данных. Когда идет обмен только интерактивным трафиком, время передачи одного байта данных в каждом направлении (в случае сжатого 5-байтового заголовка) составляет примерно 12,5 мс, при скорости 9600 бит/сек. Это хорошо укладывается в диапазон 100-200 мс, о котором мы упоминали ранее. Также заметьте, что сжатие заголовков с 40 до 5 байт уменьшает время задержки для одного байта с 85 до 12,5 мс.

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

В следующих главах мы будем использовать эти расчеты для последовательных линий, чтобы определить некоторые величины таймеров, которые используются при передаче пакетов по последовательным линиям.

Краткие выводы

В этой главе рассматривался самый нижний уровень из семейства протоколов Internet, канальный уровень. Мы рассмотрели различие между Ethernet и IEEE 802.2/802.3 инкапсуляциями, и инкапсуляцию, которая используется в SLIP и PPP. Так как оба SLIP и PPP часто используются на медленных каналах, они предоставляют методы, для сжатия общих полей (которые практически всегда неизменны). При этом улучшается время отклика.

Интерфейс loopback существует в большинстве разработок. Доступ к этому интерфейсу может быть получен через специальный адрес, обычно 127.0.0.1, или путем посылки IP датаграмм на один из собственных IP адресов хоста. Данные, отправленные в loopback интерфейс, полностью обрабатываются транспортным уровнем и IP, когда они проходят по петле по стеку протоколов.

Мы описали важную характеристику большинства канальных уровней, MTU и соответствующую концепцию транспортного MTU. Используя стандартный MTU для последовательных линий, мы вычислили временную задержку, которая существует в каналах SLIP и CSLIP.

В этой главе рассматривается только несколько общих канальных технологий, используемых сегодня в TCP/IP. Одна из причин, по которой TCP/IP успешно используется, это возможность работать поверх практически любых канальных технологий.

Упражнения

Если Ваша система поддерживает команду netstat(1) (см. главу 3, раздел "Команда netstat"), используйте ее, чтобы определить интерфейсы в Вашей системе и их MTU.

Организация канального уровня

Важнейшими задачами, решаемыми канальным уровнем модели сетевого взаимодействия (иногда этот уровень называют уровнем передачи данных ), являются задачи предоставления определенных сервисов сетево­му уровню. Основным сервисом является передача данных от сетевого уровня пе­редающей вычислительной машины сетевому уровню принимающей машины. На передающей ма­шине работает процесс, который передает биты с сетевого уровня на канальный уровень для передачи их по назначению. Работа канального уров­ня заключается в передаче этих битов на принимающую маши­ну так, чтобы они могли быть переданы сетевому уровню принимающей машины. Физически данные передаются по реальным каналам передачи, как схематично пока­зано на рис. 8.1.а. Однако посредством протоколов канального уровня виртуальный путь передачи данных связывает канальные уровни пе­редающей и принимающей вычислительной машины (рис. 8.1.б ).

Рис. 8.1. Пути передачи данных: а – виртуальный; б – фактический

Протоколы канального уровня описывают, каким образом логические биты или символы, передаваемые физическим уровнем, объединяются в более крупные единицыкадры . Обобщенная структура кадра показана на рис. 8.2. В общем случае, каждый кадр содер­жит заголовок, поле данных и трейлер (или так называемый «концевик» ). Управление кадрами – одна их главнейших функций работы канального уровня.

Рис. 8.2. Обобщенная структура кадра протокола канального уровня

Канальный уровень может предоставлять различные сервисы и их на­бор может быть разным для разных систем. Обычно рассматриваются следующие возможные вари­анты:

1) сервис без подтверждений приема кадров и без установления соединения;

2) сервис с подтверждениями приема кадров и без установления соединения;

3) сервис с подтверждениями приема кадров и с установлением соединения.

Сервис без подтверждений приема кадров и без установления соединения заключается в том, что передающая машина посылает независимые кадры принимающей машине, а принимающая машина не посылает подтверждений о приеме кадров. Никакие соединения заранее не устанавливаются и не разрываются после передачи кад­ров. Если какой-либо кадр теряется из-за помех в линии связи, то на канальном уровне не предпринимается никаких попыток восстановить его. Данный класс сервисов приемлем при очень низком уровне ошибок. В этом случае вопросы, связанные с восстановлением потерянных при передаче данных, могут быть переданы для решения верхним уровням. Этот класс сервисов также применяется в линиях связи реального вре­мени (например, при передаче речи), в которых явно предпочтительнее получить искаженные данные, чем получить их с большой задержкой. Сервис без подтверждений и без установления соединения используется на канальном уровне в большинстве локаль­ных сетей.

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

Наиболее сложным сервисом, который может предоставлять канальный уровень, является сервис, ориентированный на установление соединения с подтверждения­ми приема кадров . При использовании этого метода источник и приемник, прежде чем пере­дать друг другу данные, устанавливают соединение. Каждый посылаемый кадр нумеруется, а канальный уровень гарантирует, что каждый посланный кадр дей­ствительно принят на другой стороне линии связи. Кроме того, гарантируется, что каждый кадр был принят всего один раз и что все кадры были получены в правильном порядке. В сервисе без установления соединения, напротив, возмож­но, что при потере подтверждения один и тот же кадр будет послан несколько раз и, следовательно, несколько раз получен. При использовании ориентированного на соединение сервиса передача дан­ных состоит из трех фаз. В первой фазе устанавливается соединение, при этом обе стороны инициализируют переменные и счетчики, необходимые для слежения за тем, какие кадры уже приняты, а какие – еще нет. Во второй фазе передаются кадры данных. Наконец, в третьей фазе соединение разрывается и при этом освобождаются все переменные, буферы и прочие ресурсы, использо­вавшиеся во время соединения.

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

Обычно канальный уровень разбивает поток битов на отдельные кадры и считает для каждого кадра так называемую контрольную сумму. Когда кадр прибывает в пункт назначения, его контрольная сумма подсчитывается снова. Если она отличается от содержащейся в кадре, то канальный уровень «понимает», что при переда­че кадра произошла ошибка, и принимает соответствующие меры (например, игнорирует испор­ченный кадр и посылает передающей машине сообщение об ошибке). Разбиение потока битов на отдельные кадры представляет собой не очень простую задачу. Один из способов раз­биения на кадры заключается во вставке временных интервалов между кадрами, подобно тому, как вставляются пробелы между словами в тексте. Однако сети редко предоставляют гарантии сохранения временных интервалов при передаче данных, поэтому возможно, что эти интервалы при передаче исчезнут или, на­оборот, будут добавлены новые интервалы. Поэтому для повышения надежности передачи данных предложены более совершенные методы. Среди них наиболее популярны такие методы маркировки границ кадров (формирования кадров ), как:



1) подсчет количества символов;

2) применение сигнальных байтов с символьным заполнением;

3) использование стартовых и стоповых битов с битовым заполнением;

4) использование запрещенных сигналов физического уровня.

Первый метод формирования кадров использует поле в заголовке для указа­ния количества символов в кадре. Когда канальный уровень на принимаю­щей машине видит это поле, он узнает, сколько символов последует, и таким образом определяет, где находится конец кадра. Недостаток такого метода заключается в том, что при передаче может быть искажен сам счетчик. Тогда принимающая машина потеряет синхронизацию и не сможет обнаружить начало следующего кадра. Даже если контрольная сумма не совпадет и принимающая машина «пой­мет», что кадр принят неверно, то она все равно не сможет определить, где начало следующего кадра. Запрашивать повторную передачу кадра также бесполезно, поскольку принимающая машина не «знает», сколько символов нужно пропус­тить до начала повторной передачи. По этой причине метод подсчета символов теперь практически не применяется.

Второй метод формирования кадров решает проблему восстановления син­хронизации после сбоя при помощи маркировки начала и конца каждого кадра специальными байтами. В последнее время большинство протоколов перешло на использова­ние в обоих случаях одного и того же байта, называемого флаговым . Таким образом, если приемник теряет синхронизацию, ему необходимо просто найти флаговый байт, с помощью которого он распозна­ет конец текущего кадра. Два соседних флаговых байта говорят о том, что кон­чился один кадр и начался другой. Однако этот метод иногда приводит к серьезным проблемам при передачи бинарных данных, таких как объектные коды программ или числа с плавающей запятой. В передаваемых данных вполне может встретиться последовательность, исполь­зуемая в качестве флагового байта. Возникновение такой ситуации, скорее всего, собьет синхронизацию. Одним из способов решения проблемы является добав­ление специального escape-символа (знака переключения кода – ESC ) непосред­ственно перед байтом, случайно совпавшим с флаговым байтом внутри кадра. Канальный уровень получателя вначале убирает эти escape-символы, затем переда­ет кадр на сетевой уровень. Этот метод называется символьным заполнением. Таким образом, настоящий флаг можно отличить от «случайно совпавшего» по наличию или отсутствию перед ним символа ESC. Если же и символ ESC случайно окажется среди прочих данных, то перед этим фиктивным escape-символом также вставляется настоящий. Тогда любой одиночный ESC будет частью escape-после­довательности, а двойной будет указывать на то, что служебный байт случайно оказался в потоке данных. После очищения от вставных символов байтовая последовательности в точности совпадает с исходной. Главный недостаток этого метода заключается в том, что он тесно связан с 8-битными символами. Между тем не во всех кодировках один символ соответ­ствует 8 битам. Например, кодировка UNICODE использует 16-битное кодирование.

Следующий метод позволяет использовать кадры и наборы символов, состоящие из любого количества битов. При этом каждый кадр начинается и завершается специальной последовательностью битов 01111110. Битовое заполнение аналогично символьному, при котором в кадр перед случайно встретившимся среди данных флагом вставляется escape-символ. Битовое заполнение, как и сим­вольное, является абсолютно прозрачным для сетевого уровня обеих машин. Если флаговая последовательность битов (01111110) встречается в данных пользователя, она передается в виде 011111010, но в памяти принимающего ком­пьютера сохраняется опять в исходном виде: 01111110. Благодаря битовому заполнению границы между двумя кадрами могут быть безошибочно распознаны с помощью флаговой последовательности. Таким образом, если приемная сторона потеряет границы кадров, ей нужно всего лишь оты­скать в полученном потоке битов флаговый байт, поскольку он встречается толь­ко на границах кадров и не может присутствовать в данных пользователя.

Наконец, последний из рассматриваемых методов формирования кадров приемлем только в сетях, в которых физический носитель обладает некоторой избыточностью. Например, некоторые локальные сети кодируют один бит данных двумя физическими бита­ми. Так в «манчестерском» коде бит 1 кодируется парой высокого и низкого уровней сигналов (от­рицательный перепад), а бит 0 – наоборот, парой низкого и высокого уровней (положительный перепад). В такой схеме каждый передаваемый бит данных со­держит в середине переход, благодаря чему упрощается распознавание границ битов. Комбинации уровней сигналов (низкий–низкий и высокий–высокий) не используются для передачи данных, но используются в качестве ограничителей кадров в некоторых протоколах.

Отметим, что многие современные протоколы пе­редачи данных для повышения надежности применяют комбинированные методы формирования кадра.

Канальный уровень должен выполнять ряд специфических функций, к которым относятся обработка ошибок передачи данных и управление потоком данных , исключающее «затопление» медленных прием­ников быстрыми передатчиками.

Серьезной проблемой является гарантированная доставка сетевому уровню принимающей машины всех кадров с расположением их при этом в правильном порядке. Обычно для гарантирования надежной доставки поставщику посылается ин­формация о том, что происходит на другом конце линии. Протокол требует от получателя посылать обратно специальные управляющие кадры, содержащие по­зитивные или негативные сообщения о полученных кадрах. Получив позитивное сообщение, отправитель узнает, что посланный им кадр успешно получен на другом конце линии. Негативное сообщение, напротив, означает, что с кадром что-то слу­чилось и его нужно передать снова. Кроме того, посланный кадр может из-за неисправности оборудования или какой-нибудь помехи пропасть полностью. В этом случае при­нимающая сторона его просто не получит и, соответственно, никак не прореаги­рует, а отправитель при этом может бесконечно долго ожидать положительного или отрицательного ответа. Чтобы избежать зависаний сети в случае полной потери кадров, используют­ся таймеры канального уровня. После посылки кадра включается таймер и отсчитывает интервал времени, достаточный для получения принимающей машиной этого кадра, его обработки и посылки обратно подтверждения. В нор­мальной ситуации кадр правильно принимается, а подтверждение посылается на­зад и вручается отправителю, прежде чем истечет установленный интервал вре­мени, и только после этого таймер отключается. Однако если либо кадр, либо подтверждение теряется по пути, то установлен­ный интервал времени истечет, и отправитель получит сообщение о возможной проблеме. Самым простым решением для отправителя будет послать кадр еще раз. Однако при этом возникает опасность получения одного и того же кадра не­сколько раз канальным уровнем принимающей машины и повторной передачи его сетевому уровню. Чтобы этого не случилось, необходимо последо­вательно пронумеровать отсылаемые кадры, так чтобы получатель мог отличить повторно переданные кадры от оригиналов. Вопрос управления таймерами и порядковыми номерами, гарантирующими, что каждый кадр доставлен сетевому уровню принимающей машины ровно один раз, является очень важной задачей, решае­мой канальным уровнем.

Еще один важный аспект разработки канального уровня (а также более вы­соких уровней) связан с вопросом о том, что делать с отправителем, который по­стоянно желает передавать кадры быстрее, чем получатель способен их получать. Такая ситуация может возникнуть, если у передающей стороны оказывается бо­лее мощная (или менее загруженная) машина, чем у принимающей. При этом отпра­витель будет продолжает посылать кадры на высокой скорости до тех пор, пока получа­тель не окажется, как говорят, «затоплен» ими. Даже при идеально работающей линии связи в определенный момент времени получатель просто не сможет продолжать обработ­ку прибывающих кадров и начнет их терять. Для предотвраще­ния подобной ситуации чаще всего применяются два подхода. При первом, называющемся управлением потоком с обратной связью , получатель отсылает отправителю информацию, разрешающую последнему продолжить передачу или, по крайней мере, сообщающую о том, как идут дела у получателя. При втором подходе – управ­лении потоком с ограничением – в протокол встраивается механизм, ограничи­вающий скорость, с которой передатчики могут передавать данные, а обратная связь с получателем отсутствует. Известны различные схемы управления потоком с обратной связью, но большин­ство из них используют один и тот же принцип. Протокол содержит четко оп­ределенные правила, определяющие, когда отправитель может посылать следую­щий кадр. Эти правила часто запрещают пересылку кадра до тех пор, пока получатель не даст разрешения (явно либо неявно).

Стандарт ANSI С расширяет концепцию предварительного описания функции. Данное расширенное описание называется прототипом функции.

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

тип имя_функции (список параметров);

Прототип обычно помещается в начало программы и должен появляться перед любым вызовом функции.

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

Если возможно, С автоматически преобразует тип аргумента в тип, получаемый параметром. Тем не менее, некоторые преобразования типов недопустимы. Если функция имеет прототип, то все нелегальные преобразования будут найдены и появится сообщение об ошибке. В качестве примера, следующая программа вызывает сообщение об ошибке, поскольку пытается вызвать func() с указателем, а не с требуемым float. (Нельзя преобразовать указатель к типу float.)

/* Данная программа использует прототипы функций для достижения строгой проверки типов при вызове func(). Программа не компилируется из-за несоответствия между типом аргументов, определенных в прототипе функции, и типом аргументов, используемых при вызове функции. */

#include

int main(void)
{
int x, *y;
x = 10;
у = &x;
func(x, у) ; /* несоответствие типов */
return 0;
}

Float func (int x, float y)
{
printf("%f", у/(float)x);
return у/(float) x;
}

Использование прототипов также позволяет компилятору выдавать сообщение в случае, если число используемых при вызове функции аргументов не соответствует числу параметров, определенных в функции. Например, следующая программа не будет компилироваться, поскольку func() вызывается с неправильным числом аргументом:

/* Программа не компилируется из-за несоответствия между числом параметров, определенных в прототипе функции, и числом аргументов, используемых при вызове функции. */

#include
float func (int x, float у); /* прототип */
int main(void)
{
func (2, 2.0, 4); /* неверное число аргументов */
return 0;
}

Float func {int x, float y)
{
printf ("%f", у/(float)x);
return у/(float) x;
}

С технической точки зрения при создании прототипа функции не требуется включать настоящие имена параметров. Например, следующие два варианта абсолютно корректны и равнозначны:

Char func (char *, int);

Char func (char *str, int count);

Тем не менее, если включить имена параметров, компилятор использует имена для выдачи сообщений о несоответствии типов.

Некоторые функции типа printf() могут принимать переменное число аргументов. Переменное число аргументов определяется в прототипе с помощью многоточия. Например, прототип функции printf() выглядит так:

Int printf(const char *fmt, ...);

Для создания функции с переменным числом аргументов надо обратиться к описанию стандартной библиотечной функции va_arg().

Следует использовать прототипы функций для предотвращения ошибок, появляющихся в результате вызовов функций с неправильными аргументами. Они также помогают проверить правильность работы программы, не позволяя функциям вызываться с неправильным числом аргументов. Как ранее упоминалось, они требуются в С++.


В современных, правильно написанных программах на языке С каждую функцию перед использованием необходимо объявлять. Обычно это делается с помощью прототипа функции . В первоначальном варианте языка С прототипов не было; но они были введены уже в Стандарт С89. Хотя прототипы формально не требуются, но их использование очень желательно. (Впрочем, в C++ прототипы обязательны !) Во всех примерах этой книги имеются полные прототипы функций. Прототипы дают компилятору возможность тщательнее выполнять проверку типов, подобно тому, как это делается в таких языках как Pascal. Если используются прототипы, то компилятор может обнаружить любые сомнительные преобразования типов аргументов, необходимые при вызове функции, если тип ее параметров отличается от типов аргументов. При этом будут выданы предупреждения обо всех таких сомнительных преобразованиях. Компилятор также обнаружит различия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции.

В общем виде прототип функции должен выглядеть таким образом:

тип имя_функции (тип имя_парам1, тип имя_парам2, ..., имя_парамN );

Использование имен параметров не обязательно. Однако они дают возможность компилятору при наличии ошибки указать имена, для которых обнаружено несоответствие типов, так что не поленитесь указать этих имен - это позволит сэкономить время впоследствии.

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

/* В этой программе используется прототип функции чтобы обеспечить тщательную проверку типов. */ void sqr_it(int *i); /* прототип */ int main(void) { int x; x = 10; sqr_it(x); /* несоответствие типов */ return 0; } void sqr_it(int *i) { *i = *i * *i; }

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

#include /* Это определение будет также служить и прототипом внутри этой программы. */ void f(int a, int b) { printf("%d ", a % b); } int main(void) { f(10,3); return 0; }

В этом примере специальный прототип не требуется; так как функция f() определена еще до того, как она начинает использоваться в main() . Хотя определение функции и может служить ее прототипом в малых программах, но в больших такое встречается редко - особенно, когда используется несколько файлов. В программах, приведенных в качестве примеров в этой книге, для каждой функции автор старался приводить отдельный прототип потому, что именно так обычно и пишется код на языке С.

Единственная функция, для которой не требуется прототип - это main() , так как это первая функция, вызываемая в начале работы программы.

Имеется небольшая, но важная разница в том, как именно в С и C++ обрабатывается прототип функции, не имеющей параметров. В C++ пустой список параметров указывается полным отсутствием в прототипе любых параметров. Например,

Int f(); /* Прототип C++ для функции, не имеющей параметров */

Однако в С это выражение означает нечто другое. Из-за необходимости придерживаться совместимости с первоначальной версией С пустой список параметров сообщает, что просто о параметрах не предоставлено никакой информации . Что касается компилятора, то для него эта функция может иметь несколько параметров, а может не иметь ни одного. (Такой оператор называется старомодным объявлением функции, он описан в следующем разделе.)

Если функция в языке С не имеет параметров, то в ее прототипе внутри списка параметров стоит только ключевое слово void . Вот, например, прототип функции f() в том виде, в каком он должен быть в программе на языке С:

Float f(void);

Таким образом компилятор узнает, что у функции нет параметров, и любое обращение к ней, в котором имеются аргументы, будет считаться ошибкой. В C++ использование ключевого слова void внутри пустого списка параметров также разрешено, но считается излишним.

Прототипы функций позволяют «отлавливать» ошибки еще до запуска программы. Кроме того, они запрещают вызов функций при несовпадении типов (т.е. с неподходящими аргументами) и тем самым помогают проверять правильность программы.

И напоследок хотелось бы сказать следующее: так как в ранних версиях С синтаксис прототипов в полном объеме не поддерживался, то в С прототипы формально не обязательны. Такой подход необходим для совместимости с С-кодом, созданным еще до появления прототипов. Но если старый С-код переносится в C++, то перед компиляцией этого кода в него необходимо добавить полные прототипы функций. Помните, что хотя прототипы в С не обязательны, но они обязательны в C++. Это значит, что каждая функция в программе на языке C++ должна иметь полный прототип. Поэтому при написании программ на С в них указываются полные прототипы функций - именно так поступает большинство программистов, работающих на этом языке.

Старомодные объявления функций

В «ранней молодости» языка С, еще до создания прототипов функций, все-таки была необходимость сообщить компилятору о типе результата функции, чтобы при вызове функции был создан правильный код. (Так как размеры разных типов данных разные, то размер типа результата надо было знать еще до вызова функции.) Это выполнялось с помощью объявления функции, не содержащего никакой информации о параметрах. С точки зрения теперешних стандартов этот старомодный подход является архаичным. Однако его до сих пор можно найти в старых кодах. По этой причине важно понимать, как он работает.

Согласно старомодному подходу, тип результата и имя функции, как показано ниже, объявляются почти что в начале программы:

#include double div(); /* старомодное объявление функции */ int main(void) { printf("%f", div(10.2, 20.0)); return 0; } double div(double num, double denom) { return num / denom; }

Старомодное объявление типа функции сообщает компилятору, что функция div() возвращает результат типа double . Это объявление позволяет компилятору правильно генерировать код для вызовов этой функции. Однако оно ничего не говорит о параметрах div() .

Общий вид старомодного оператора объявления функции такой:

спецификатор_типа имя_функции ();

Обратите внимание, что список параметров пустой. Даже если функция принимает аргументы, то ни один из них не перечисляется в объявлении типа.

Как уже говорилось, старомодное объявление функции устарело и не должно использоваться в новом коде. Кроме того, оно несовместимо с C++.

Прототипы старомодных библиотечных функций

Любая стандартная библиотечная функция в программе должна иметь прототип. Поэтому для каждой такой функции необходимо ввести соответствующий заголовок. Все необходимые заголовки предоставляются компилятором С. В системе программирования на языке С библиотечными заголовками (обычно) являются файлы, в именах которых используется расширение.h . В заголовке имеется два основных элемента: любые определения, используемые библиотечными функциями, и прототипы библиотечных функций. Например, почти во все программы из этой книги включается файл , потому что в этом файле находится прототип для printf() . Заголовки для стандартных функций описаны в