Проектирование сетевых протоколов
Введение
Вопрос-ответ
Эти протоколы основаны на общении небольшими порциями данных. Протокол общения обычно сильно типизирован. В качестве примера можно привести всем известные AT-команды для модемов.
Данный тип протоколов является наиболее простым для обработки (требуется элементарный разбор строки с вычленением данных). Но общаться таким протоколом на более-менее серьезных задачах не очень легко. Таки протоколы хорошо подходят для пересылки небольших порций данных скалярных типов (строки, числа).
Тем не менее проектировать подобные протоколы тоже необходимо.
Пример
Нам необходимо передавать с клиента на сервер по 10 чисел, и получать в ответ строку либо число (варьируется от входных данных). Для этого нам необходимо: стадия «рукопожатия», стадия пересылки данных, стадия получения ответа.
«Рукопажатие»: вполне достаточно пересылки слова «HELLO» туда и обратно (если мы знаем что есть вероятность вместо сервера попасть на другого клиента, то можно разделить клиентское и серверное приветствие, например «HELLOCL» (от клиента) и «HELLOSRV» (от сервера)). Обработка элементарная и сводится к строковому сравнению.
Пересылка данных: возьмем команду «SEND x1 x2 x3 x4 x5 x6 x7 x8 x9 x10» как команду посылки от клиента и «OK SEND» как ответ сервера. Обработка опять же элементарная и сводится к строковому сравнению и вызову sscanf().
Получение ответа: условимся, что сервер посылает «ANSWER STR строка» (если ответ — строка) или «ANSWER NUM число» (если ответ — число). Клиент отвечает командой «OK ANSWER».
Казалось бы все просто и понятно. НО. Ведь таким образом мы не сможем понять к какому набору чисел относится присланный ответ. Решить эту проблему просто. При пересылке данных будем использовать команды: «SEND id NUMS x1 x2 x3 x4 x5 x6 x7 x8 x9 x10» и «OK SEND id», где id — это уникальный идентификатор этого набора чисел. При ответе соответственно команды будут следующими: «ANSWER id STR строка», «ANSWER id NUM число», «OK ANSWER id». Такой протокол уже будет исчерпывающим в большинстве случаев.
Структуры
Данный тип протокола является наиболее распространенным. Его основой является жесткая типизация порции отправляемых данных. То есть мы заранее уславливаемся, что во всех пакетах по такому-то смещению и такой-то длины будут лежать такие-то данные (смещение и длина некоторых полей могут также задаваться в структуре, но в основном используются изначально заданные смещения). Такими протоколами являются практически все низкоуровневые протоколы.
Несомненным плюсом таких протоколов (особенно при условии жестко заданных смещений и длин всех полей) является крайняя простота обработки. Нам необходимо всего лишь проверить размер и выполнить команду memcpy() в экземпляр структуры, соответствующей нашему пакету.
При проектировании подобных протоколов необходимо помнить об особенностях хранения структур в C. Я имею в виду то, что называется packing. Дело в том, что любой экземпляр любой структуры должен быть выравнен в памяти с некоторой кратностью (кратность задается для всей программы одна). По умолчанию используется кратность 4 (менять не советую, так как это значение сильно влияет например на неймспейс std). Это означает, что размер любой структуры всегда будет кратен 4 (если размер структуры был 14, то в конец допишутся 2 ничего не значащих байта и размер станет равен 16). Следовательно нам необходимо позаботиться об одинаковом значении этого параметра на сервере и клиенте.
Так же основной ошибкой при проектировании подобных протоколов является невнимательность к хранению многобайтных типов в памяти. Необходимо помнить что x86 хранит их в виде little-endian (от младшего к старшему), а по стандартам сетевых протоколов и например в компьютерах SPARC необходимо хранить их в виде big-endian (от старшего к младшему). Таким образом нам необходимо знать в каком порядке к нам придут многобайтные типы и при необходимости их переворачивать. Причем, если нам необходима высокая скорость, у нас большой поток обмениваемыми данными и мы не можем уйти от вращений (критерии например акутальны при разработке кросс-архитектурной системы распределенных вычислений), то необходимо уделить функции поворота лишних полчаса, но написать ее максимально быстрой. В таких случаях стандартные htonl(), ntohl() могут не успеть.
К теговым протоколам я отношу ныне модные XML-подобные протоколы. Такие протоколы хоть и являются крайне избыточными, но тем не менее легко обрабатываются и являются абсолютно гибкими. Основными их проблемами являются избыточность и не очень высокая скорость обработки.
Основной ошибкой проектирования подобных протоколов является желание впихнуть все и сразу. Необходимо же как можно четче сформулировать требования к протоколу и вычленить то подмножество функционала, которое действительно необходимо. Такой подход к проектированию возможно и является не слишком расширяемым, но зато позволит нам сэкономить на времени обработки. Тем более, при грамотном проектировании модуля разборщика мы можем свести проблему расширяемости к минимуму (добавить пару функций и проверок в общий код).
Лично мне (в силу специализации на требовательных к скорости и жестко структурированных сетевых приложениях) подобный подход кажется излишним и расточительным.
Теги+структуры
Пожалуй самый интересный тип протоколов. Позволяет объединить высокую скорость разбора и гибкость.
Пакеты данного протокола разделяются по типам. Также можно спроектировать дерево подчинения типов (например пакет А может входить только в пакет В, а отдельно идти не может). Основой таких пакетов является жестко заданная заголовочная структурная часть (для каждого типа своя)+нежесткая часть данных (такой же подход бывает используется и в структурных данных при переменной длине последнего параметра в структуре).
Разбор таких протоколов хоть и сложнее, чем разбор структурных протоколов, но легче и быстрее чем разбор чисто теговых протоколов.
Обычно тип пакета пишется первым полем в заголовочной части и нам достаточно считать его и вызвать необходимую функцию, которая скопирует заголовок в структуру, а данные в кусок памяти (обычно достаточно char*, но в некоторых случаях удобнее копировать сразу в массив неких структур).
Основной ошибкой при проектировании является желание сделать «как в теговом протоколе» и создать кучу разных типов с мелкими заголовками. Это приводит к потере высокой скорости разбора и сводит на нет все преимущества этого типа. Таким образом необходимо балансировать между неповоротливостью и низкой скоростью. Как показала практика, в большинстве случаев является идеальным разбитие на теги такое же как разбитие на классы, если бы этот протокол был бы обычной структурой данных.
Создание протокола прикладного уровня
Доброго времени суток, форумчане!
Недавно решил поставить себе цель написать собственный протокол прикладного уровня. Протокол хочу использовать для передачи данных через сеть(как медиафайлы, так и какие либо команды). Имею примерное представление того, как будет выглядеть отсылаемый пакет данных, но, к сожалению, не знаю какой язык использовать и как, примерно, это всё реализуется. Буду при знателен, если вы меня подталкнёте в правильное русло.
Создание сетевого протокола в игровом клубе
Нужно создать протокол или службу (не знаю как это правильно назвать) который будет предназначен.
Нужен совет по протоколам прикладного уровня
Подскажите где можно прочитать про создание протоколов прикладного уровня? может эта тема уже.
Создание программы-протокола
Привет все. Я ЛАМО в VBS, но мне необходимо замутить прогу-прикол, поможите чем сможете.
Создание 2д уровня
Здравствуйте! Требуется создать карту для 2д игры такую, чтобы текстура была со всех 4 сторон.
Язык по сути не важен. Главное чтобы в нем была возможность работы с сетью (или хотя бы доступ к API OS).
Думаю что стоит начать с изучения уже имеющихся, например, протокола передачи данных в торрент-сети. Он не сложный (меньше 10 команд) и позволяет передавать как данные (файлы), так и служебные сообщения.
Все остальные сообщения в протоколе принимают форму
Конструкторское замечание: Это строгое определение, в реальности some games may be played. В частности, поскольку крайне маловероятно, чтобы пиры загружали куски, которые они уже имеют, пир может не рекламировать (advertise) наличие кусков пирам, которые эти куски имеют. Подавление HAVE-сообщений («HAVE supression») как минимум приведет к 50% сокращению числа сообщений, а это сокращение примерно на 25-35% накладных расходов протокола (protocol overhead). В то же время, возможно целесообразно отправить HAVE-сообщение пирам, которые уже имеют этот кусок, поскольку он будет полезен в определении его редкости.
Вредоносные пиры также могут выбирать оглашение (advertise) имеющихся кусков, которые пир точно никогда не загрузит. Due to this attempting to model peers using this information is a bad idea
Bitfield сообщение может быть направлено сразу же после того, последовательность «рукопожатий» будет завершена, и до любых других сообщений. Оно является необязательным, и клиентам, не имеющих куски, нет нужды отсылать его.
Bitfield неверной длины считается ошибочным. Клиенты должны разорвать соединение, если они получают bitfields неверного размера, или если bitfield имеет произвольный набор запасных битов.
Сообщение-запрос фиксированной длины, используется для запросы блока. Полезная нагрузка сообщения содержит следующую информацию:
index: целое число, определяющее с указанием нулей (zero-based) индекс куска
begin: целое с указанием нулей смещение байтов внутри куска
length: целое число, определяющее запрашиваемую длину.
This section is under dispute! Please use the discussion page to resolve this!
View 1. Согласно официальной спецификациям, «Все текущие реализаций используют 2^15 (32KB) куски, и закрывают соединения, которые запрашивают количество данных более 2^17 (128Kb).» Уже в версии 3 или 2004, это поведение было изменено на использование 2^14 (16Кб) блоков. Начиная с версии 4.0 или mid-2005, соединение в Mainline при запросах больше, чем 2^14 (16Кб), и некоторые клиенты последовали этому примеру. Помните, что block-запросы меньше, чем куски (>= 2^18 байт), поэтому будут необходимы многочисленные запросы, чтобы скачать весь кусок.
Собственно, спецификация позволяет 2^15 (32Кб) запросы. Реальность такова, что все клиенты начиная с сегодняшнего момента будут использовать 2 ^ 14 (16Кб) запросы. Из-за клиентов, которые привязаны к такому размеру запросов, рекомендуется реализовывать программы, делающие запросы именно такого размера. Меньшие размеры запросов приводят к повышению накладных расходов в связи с увеличением количества требуемых запросов, проектировщики советуют не делать размер запросов меньше, чем 2 ^ 14 (16Кб).
index: целое определяющее с указанием нулей индекс куска
begin: целое, определяющее с указанием нулей байтовое смещение внутри куска
block: блок данных, который есть подмножество куска с определённым индексом
Cancel-сообщение фиксированной длины, используется для отмены блокировки запросов. Полезная нагрузка сообщения идентична той, которая была в «сообщении-запросе» («request» message). Сообщение обычно используется во время стратегии «Конца игры» (End game, см. ниже раздел Алгоритмы).
Port-сообщение отсылается посредством новых версий Mainline, которая реализует DHT Tracker. Порт для прослушивания является портом который DHT узел прослушивает. Этот пир должен быть вставлен в локальную таблицу маршрутизации (если DHT Tracker поддерживается).
Главное понять саму суть устройства подобных протоколов. Через сеть вы передаете и принимаете массив (поток) данных и в них должны быть специальные метки чтобы можно было этот массив разобрать на приемной стороне. Среди таких моток должны быть как минимум, команда (тип данных) и длина пакета с данными. Таким образом, при приеме массива данных из сети, будет информация, во первых о типе пакета, необходимая для его правильной обработки, а во вторых, длина пакета, необходимая чтобы точно знать каков его размер. Это можно описать примерно такой структурой (ЯП в данном случае не имеет значения).
Эта структура является заголовком пакета и должна быть в его начале, и за ней следуют данные. Поле Size определяет размер пакета. В данном случае, его размер не может превышать 64 КБ, но если увеличить размер поля до 4-ёх байт, то появится возможность передавать пакеты имеющие размер до 4 ГБ. Но это нежелательно, поскольку если потеряется пакет не большого размера, то его быстрее можно будет повторно отправить. Поле Type определяет тип пакета (команду), необходимую для правильной обработки пакета на приемной стороне.
Если протокол реализован поверх UDP, или другого низкоуровневого протокола, то не помешает добавить так же поле с контрольной суммой пакета, чтобы удостоверится в целостности данных.
Есть еще один момент, на который необходимо обратить внимание. Пакет может быть принят не за один раз (вызов принимающей функции из сети) или наоборот, за раз может быть принято несколько пакетов, причем последний из них может быть принят не полностью. Поэтому так же необходим промежуточный буфер, в который будут записываться все данные, принимаемые из сети, а затем, по мере получения целых пакетов, уже обрабатываться.
Реализация в коде (сервер и клиент) всего вышенаписанного. Для примера, реализованы две команды:
Протокол своими руками. Создаем с нуля TCP-протокол и пишем сервер на C#
Содержание статьи
Итак, протокол передачи данных — это соглашение между приложениями о том, как должны выглядеть передаваемые данные. Например, сервер и клиент могут использовать WebSocket в связке с JSON. Вот так приложение на Android могло бы запросить погоду с сервера:
И сервер мог бы ответить:
Пропарсив ответ по известной модели, приложение предоставит информацию пользователю. Выполнить парсинг такого пакета можно, только располагая информацией о его строении. Если ее нет, протокол придется реверсить.
Создаем базовую структуру протокола
Этот протокол будет базовым для простоты. Но мы будем вести его разработку с расчетом на то, что впоследствии его расширим и усложним.
Почти каждый бинарный протокол имеет свое «магическое число» (также «заголовок» и «сигнатура») — набор байтов в начале пакета. Оно используется для идентификации пакетов своего протокола. Остальные пакеты будут игнорироваться.
Каждый пакет будет иметь тип и подтип и будет размером в байт. Так мы сможем создать 65 025 (255 * 255) разных типов пакетов. Пакет будет содержать в себе поля, каждое со своим уникальным номером, тоже размером в один байт. Это предоставит возможность иметь 255 полей в одном пакете. Чтобы удостовериться в том, что пакет дошел до приложения полностью (и для удобства парсинга), добавим байты, которые будут сигнализировать о конце пакета.
Завершенная структура пакета:
XPROTOCOL PACKET STRUCTURE
(offset: 0) HEADER (3 bytes) [ 0xAF, 0xAA, 0xAF ]
(offset: 3) PACKET ID
(offset: 3) PACKET TYPE (1 byte)
(offset: 4) PACKET SUBTYPE (1 byte)
(offset: 5) FIELDS (FIELD[])
(offset: END) PACKET ENDING (2 bytes) [ 0xFF, 0x00 ]
(offset: 0) FIELD ID (1 byte)
(offset: 1) FIELD SIZE (1 byte)
(offset: 2) FIELD CONTENTS
Пишем клиент и сервер
Для начала нужно ввести основные свойства, которые будет иметь пакет:
Добавим класс для описания поля пакета, в котором будут его данные, ID и размер.
Сделаем обычный конструктор приватным и создадим статический метод для получения нового экземпляра объекта.
Теперь пора научиться парсить пакеты.
Минимальный размер пакета — 7 байт: HEADER (3) + TYPE (1) + SUBTYPE (1) + PACKET ENDING (2)
Проверяем размер входного пакета, его заголовок и два последних байта. После валидации пакета получим его тип и подтип.
У кода выше есть проблема: если подменить размер одного из полей, парсинг завершится с необработанным исключением или пропарсит пакет неверно. Необходимо обеспечить безопасность пакетов. Но об этом речь пойдет чуть позже.
Учимся записывать и считывать данные
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Ликбез по программированию. Сети и сетевые протоколы
Компьютер — это инструмент, которым нужно уметь пользоваться. Не секрет, что современное коммерческое программирование отличается от теоретического, и приложением «Hello, World!» вы никого не удивите — существуют потребности иного толка и другого уровня. В новообразованную рубрику (или цикл статей) пишут не только студенты, но и просто люди, которые хотят научиться реально программировать. Часть из них хочет воплотить в жизнь какие-нибудь планы, другая — приобрести дополнительную профессию. Приступим…
Достаточно много вопросов задается по сетям, протоколам. Конечно, часто вообще стесняются показать незнание и спросить. А прочитать ликбез в газете доступно каждому:). Постараемся объяснить все на максимально понятном уровне. Конечно, по сетям написано n-e количество полезных и объемных книг, но наш материал для многих может стать просто хорошей точкой входа в тему. Мы не претендуем на лавры «полного подробного руководства».
Также мы не станем заниматься попытками объять необъятное, а обратимся к письму Андрея из Минска, который сетовал на то, что локальные приложения он может программировать легко, но когда речь «заходит об Интернете»… 🙂 Само письмо приводить не будем, отмечу только, что сети — это дополнительный элемент в современном программировании, не более того. То есть работа с ними ничего особенно сложного не представляет. Изначально стоит сказать, что протоколов — море, но при этом существуют стандартные взаимосвязанные цепочки, и выбрав, например, вариант HTTP (FTP, Gopher и т.п.) —TCP—IP, мы подразумеваем не так много технологий.
Просто очень часто начинающих отпугивает именно количество аббревиатур.
Давайте немного расслабимся и опишем обыкновенную ситуацию в виде сценки.
Представьте себе офис какой-либо фирмы, занимающейся, например, продажей строительного оборудования. У нее есть своя мини-АТС, которая объединяет работников компании, при этом есть и общие (городские) номера, по которым звонят извне.
Вы — заказчик, ищете бетономешалку. Общение происходит так:
Набираете номер. Соединение успешно.
…
Диспетчер: «Фирма «Все для планеты». Здравствуйте!».
Вы: «Здравствуйте, мне нужно получить информацию по бетономешалкам».
Диспетчер: «Сейчас переключу…». Переключает на внутренний телефон 249.
…
Секретарь отдела: «Здравствуйте. Отдел сбыта строительной техники. Я вас слушаю».
Вы: «Здравствуйте. Мне нужно получить техническую спецификацию и прайсы на такую-то модель бетономешалки».
Секретарь отдела: «Вы от фирмы или частное лицо?».
Вы: «Я представитель фирмы N».
Секретарь отдела: «Минуточку… Переключаю».
…
Менеджер Николай: «Здравствуйте, чем могу помочь?».
Вы: «Мне нужно получить техническую спецификацию и прайсы на такую-то модель бетономешалки».
Менеджер Николай: «Хорошо, вам удобнее по e-mail или факсом?»
Ну и причем здесь пример? Прочитайте следующее:
1. Введите IP… (в нашем примере это городской номер телефона). По IP-адресу пакет доставляется до конкретного компьютера.
2. Введите порт… (в нашем примере переключение на внутренний номер отдела). По номеру порта определяется, для какой именно программы предназначается пришедший пакет. Если бы у фирмы в нашей сценке не было отдела строительной техники, то вы бы обратились, что называется, «не по адресу».
3. Введите имя… пароль… (в нашем примере вопрос о том, кто вы).
4. Проверка прав доступа, предоставление доступа к определенной папке/сервису (в нашем примере — к менеджеру).
5. Запрос на получение данных.
6. Обмен данными.
Теперь все становится более-менее понятно. Сама передача данных, включая и техническое обеспечение общения, осуществляется аппаратными средствами, как минимум телефоном, а после вам может понадобиться электронная почта или факс.
На этом примере без всяких схем и графиков можно вывести правило:
Если вам что-то нужно от кого-то, то вы — клиент, если кому-то что-то нужно от вас, то вы — сервер.
Задачей организации клиента является получение информации по определенному протоколу (в сценке вы говорили на русском языке, применяли слова «Здравствуйте», правильно отвечали на вопросы и т.п.). В информатике под протоколом понимается заданный набор правил взаимодействия. Задачей организации сервера является предоставление и обновление текущих данных, отслеживание и управление правами доступа, обеспечение достоверных ответов и адекватных действий на запросы.
Теперь перейдем к техническим деталям.
Семь или четыре? Сначала семь!
Изначально в основу теории (как, собственно, и практики) сетей и сетевых протоколов была положена модель OSI (Open Systems Interconnection), разработанная известной всем Международной организацией по стандартам ISO. Что такое модели ISO как таковые? В любых сферах — это описания технологических стандартов, на базе которых (или под которые) реализуются определенные технологии. Модели ISO справочные и носят
рекомендательный характер. Что касается конкретно сетевых взаимодействий, то в рамках OSI мы имеем семь уровней, от аппаратного (физического) до прикладного (сервисы).
Перечисляем (OSI):
1. Physical layer. Физический уровень (кабели, сетевая карта).
2. Data-link layer. Канальный уровень (передача данных между любыми узлами в сетях).
3. Network layer. Сетевой уровень (доставка пакета любому узлу в сетях).
4. Transport layer. Транспортный уровень (средства для установления соединения, буферизации, нумерации и упорядочивания пакетов).
5. Session layer. Уровень сеанса (управление диалогом между узлами).
6. Presentation layer. Уровень представления (преобразование данных, например, сжатие или шифрование).
7. Application layer. Прикладной уровень (сервисы).
В данном случае мы не используем понятия низкого и высокого уровней, чтобы вы не запутались, хотя в документациях и учебных материалах, основанных на OSI, часто под высоким подразумевают физический, а под низким — прикладной (дело в нумерации списка). А запутаться вы можете просто, поскольку в программировании аппаратный уровень всегда считается низким. Правильно же под верхним уровнем подразумевать программную часть (в нашем случае 5-7).
В общем, семь пунктов. Как это реализуется технически? Допустим, мы передаем(!) данные. На прикладном уровне к пакету добавляется(!) заголовок, потом осуществляется переход к уровню представления, где к нему опять же добавляется собственный заголовок. На уровне сеанса пакет получает еще один заголовок и так далее, пока все не доходит до физического уровня.
При получении данных идет обратное разматывание: стартует обход с физического уровня, на канальном убирается(!) соответствующий заголовок, далее идентичные действия, пока не убираются все заголовки, и пользователь получает чистый пакет без служебной информации.
На самом деле, передача данных может начинаться не с седьмого, а с четвертого уровня и т.п., все зависит от условий и используемого протокола. Первые три пункта списка реализуются на уровне оборудования (сетевые карты, маршрутизаторы, концентраторы, мосты и пр.), четвертый — промежуточный, а 5-7 реализуются в рамках операционных систем и приложений.
Пакет содержит информацию о себе и данные, которые вы хотите передать. При прохождении цепочки 7-> 1 (передача) он обрастает служебными данными, а при 1->7 (получение) эти данные удаляются теми, кому они предназначены.
Конкретный пример семи пунктов OSI
Давайте представим модель на базе конкретного примера, причем для более близкого сопоставления со структурой OSI (соответствия с семью пунктами), возьмем вариант NetBIOS на прикладном уровне. На самом деле это не так важно, ведь нам нужно понять основную структуру.
Все аббревиатуры будут расшифрованы. Итак, наши семь пунктов.
1. Физический уровень. Сетевая карта. Трансляция пакета данных.
2. Канальный уровень. Драйвер сетевой карты. NDIS. PPP/SLIP.
3. Уровень сети. IP, ARP, RARP.
4. Уровень транспорта. TCP.
5. Уровень сеанса. Интерфейс TDI.
6. Уровень представления. NetBIOS на основе TCP/IP.
7. Прикладной уровень. NetBIOS.
Теперь по порядку. С аппаратными уровнями структурно все более-менее понятно, но поскольку мы решили все расшифровывать, стоит остановиться на втором пункте.
Пункт 2. Для передачи пакетов необходимо знать аппаратный адрес получателя, или, как принято называть — MAC-адрес. Он является уникальным и прошивается в сетевом устройстве на заводе-изготовителе. То есть, другими словами, в качестве адресов на канальном уровне используются МАС- адреса устройств. Чтобы было совсем понятно, проведем параллель с мобильной связью. У вас есть свой номер, но у каждого телефона имеется и отдельный номер, данный изготовителем. Примерно так.
NDIS (Network Driver Interface Specification) — поддержка передачи данных на аппаратном уровне. Причем речь идет обо всех возможных сетевых взаимодействиях. Например, говоря «сети», мы сейчас больше подразумеваем Интернет, но коммуникации между компьютерами могут быть различными, с использованием разных технологий и устройств (Ethernet, IR, serial port). Это достаточно сложная тема для ознакомительной статьи. Подробности вы можете узнать после.
PPP (Point-to-point protocol) — коммуникационный протокол для подключения компьютера к сети. Речь идет о конкретной поддержке физического подключения, и как самый яркий пример, вы используете PPP для соединения с Интернетом. PPP подразумевает свое семейство промежуточных протоколов, благодаря которым и происходит подключение. В более старой литературе в качестве альтернативы PPP вы можете встретить протокол SLIP.
Пункт 3. IP (Internet Protocol) находится на сетевом уровне и отвечает за передачу данных. По протоколу IP пакеты просто отправляются в сеть без ожидания подтверждения о получении данных (АСК Acknowledgment). В свою очередь, пакет данных включает адреса отправителя и получателя, идентификатор протокола, TTL (время жизни пакета) и контрольную сумму для проверки целостности пакета. Здесь есть одно «но», которое многие уже могли заметить. Отправитель не может проследить не только целостность пакетов (это может узнать только получатель), но и саму гарантию доставки пакетов. Эту проблему решают другие протоколы.
На данном уровне производится переход от MAC-адресов к IP-адресам и обратно. Протокол ARP (Address Resolution Protocol, протокол определения адреса) предназначен для определения аппаратного (MAC) адреса компьютера в сети по его IP-адресу. RARP (Revers Address Resolution Protocol) работает обратно.
IP-адрес — уникальный 32-битный адрес, назначенный каждому узлу Интернета.
Пункт 4. TCP (Transmission Control Protocol) — один из основных транспортных протоколов. Он напрямую взаимодействует с IP, вернее, предоставляет в его пользование (или получает от него) TCP-пакеты. Объяснить принцип действия транспортных протоколов можно и просто. Вам нужно отправить данные, если их порция большая и не вмещается в один пакет, то они разбиваются на несколько TCP-пакетов. Сами TCP-пакеты не содержат адресов отравителя/получателя, поскольку оные присваиваются на уровне IP. То есть, «голый» TCP-пакет вы отправить не можете, потому как неизвестно кому. При этом именно TCP устраняет недостатки IP в области проверки целостности и гарантии доставки. Данный транспортный протокол организует определенную связь между отправителем и получателем, он работает по технологии «клиент-сервер».
Архитектура TCP/IP, которую вы можете найти в справке по Visual Studio (для Windows CE)
Обычно пишут просто связку TCP/IP и представляют ее как единую аббревиатуру, что во многом верно.
Пункт 5. Уровень сеанса подразумевает управление диалогом между узлами, в его рамках обеспечивается возможность фиксации активной на данный момент стороны. TDI (Transport Driver Interface) является, как бы это сказать проще, верхним уровнем NDIS. Например, NDIS подразумевает поддержку выполнения транспортировки на нижнем (аппаратном) уровне по стандартному протоколу, такому как TCP/IP, в то время как TDI отвечает за более высокий уровень. Здесь вы можете запутаться, впрочем, и информация на данный момент для вас не будет особенно актуальной.
Пункты 6 и 7. NetBIOS (Network Basic Input Output System) является набором API-функций для работы с сетью. Он работает только на программном уровне и отвечает за формирование пакетов для отправки данных, причем в большинстве случаев физически они могут передаваться либо по TCP/IP, либо по IPX/SPX (другие стандарты для сетей Novell).
Семь или четыре? Можно и четыре…
Microsoft очень часто ругают, хотя у каждой медали есть две стороны. Например, лично мне нравится, когда производятся разумные упрощения. Помните, мы говорили о том, что пункты 1-3 — аппаратные, 4 — промежуточный, 5-7 — программный? Так вот, Microsoft пошла на упрощения, фактически заменив семь пунктов на четыре, объединив все под технологией MS TCP/IP:
1. Аппаратный уровень. Пункты 1 и 2 OSI.
2. Межсетевой уровень. IP.
3. Уровень транспорта. TCP или UDP.
4. Уровень приложения. Сокеты Windows.
На самом деле, это условное разбиение, которое вы можете увидеть в некоторых книгах. Если же вы обратитесь к справке Visual Studio с запросом «TCP/IP Architectural Model», то получите 7 пунктов OSI и распределение между ними протоколов и связей (там показан пример для Windows CE). Присмотревшись, обнаружите, что упрощение возникло в силу того, что в рамках Microsoft один пункт может объединять несколько OSI.
Сокеты — это каналы связи между компьютерами, передающие данные в обоих направлениях и реализующиеся на программном уровне. То есть, вам известны IP и номер порта сервера и вы к нему подключились, но при всем этом нужно организовать канал связи, по которому будет происходить обмен данными.
Вы можете одновременно открыть несколько сокетов, и ничто не ограничивает максимальное число открытых сокетов.
В общем, мы говорим просто о программных интерфейсах, которые облегчают работу с транспортными протоколами на верхнем уровне.
Сравнение MS TCP/IP и OSI (взято из книги М. Е. Фленова «Программирование на C++ глазами хакера»)
Для проверки гарантированной доставки данных протокол TCP организует определенную связь отправителя с получателем (клиента с сервером). Происходит обмен данными, благодаря которому получатель знает количество пакетов, которые он должен принять, их очередность и так далее. Если пакет не получен, протокол посылает его заново. На самом деле, механизм общения достаточно прост, но именно из-за гарантированного качества доставки TCP/IP работает достаточно медленно.
UDP (User Datagram Protocol) объясним с помощью нескольких определений: (а) — это облегченный TCP, (б) — это TCP без проверки качества доставки, (в) — это протокол, работающий без установления связи, как говорят, просто «выбрасывающий данные в сеть». Никакой проверки качества доставки нет, если один пакет потерялся в дороге, то он и исчезает. Мало того, если говорить образно, вы посылаете: «Привет, меня зовут Сергей», а получатель может принять строку в виде: «привет, Сергей меня зовут». Но UDP быстр. Он часто используется для обеспечения живого общения (чатов), трансляций (интернет-радио). Ведь, по существу, если один пакет потеряется в пути, то вы услышите щелчок в музыкальной композиции, не более того.
К сервисам или сервисным протоколам относятся FTP, HTTP и так далее. Они позволяют взаимодействовать по определенным правилам на уровне пользовательских интерфейсов и приложений. Сервисы могут базироваться как на TCP, так и на UDP.
Помимо этого, сервисы вы можете писать и сами.
Итак, в рамках Visual Studio программирование сокетов, создание клиентских и серверных программ проще всего начать с использования класса CSocket. Он является наследником СAsyncSocket, причем работая с ним, вы по умолчанию используете TCP/IP.
После этого имеет смысл начать программировать с библиотекой Winsock или Winsock2. На этой базе вы сможете запрограммировать все, что угодно:). Также есть и библиотеки afxinet и wininet. Например, в рамках программирования сервисов, например, написания HTTP клиентов и серверов, FTP клиентов можно использовать конкретный класс CInternetConnection.
Программирование с сокетами, создание клиент-серверных связей, TCP/IP, UDP, организация передачи данных большого объема, синхронный и асинхронный режимы просто превосходно показаны в книге М. Е. Фленова «Программирование на C++ глазами хакера». Насколько мне известно, подобная есть и для Delphi (автор равно хорошо владеет двумя языками/средами). Если вам сложно освоить эти вопросы (а я видел некоторые учебные методические пособия:)))) и книги), то Фленов вам поможет. Чуть хуже все описано в справке Visual Studio.
В играх вместо сокетов раньше зачастую использовали более высокоуровневую модель — DirectPlay. DirectPlay представляет собой надстройку над сокетами, предоставляющую набор высокоуровневых функций (а, казалось бы, куда уже выше сокетов?). И хотя в старых книгах об этом говорится, на сегодня тема DirectPlay не актуальна.
Тестовое задание дано в приложении к статье.
Приложение к «Ликбез по программированию. Сети и сетевые протоколы»
В качестве практикума по программированию сокетов я составил задание, показанное на рисунке. Оно спроектировано так, чтобы, выполнив его, вы смогли сказать: «Я уже многое знаю! И во многом разбираюсь». Условно назовем эту программу «Сетевой хамелеон».
За основу возьмите Winsock2. В качестве настольного руководства рекомендуется использование книги М.Е. Фленова «Программирование на C++ глазами хакера», а также открытые коды из Интернета. Правда, если вы будете все выполнять под Visual C++ образца MSVS 2008, да и, в принципе, вообще, то для организации беспроблемной работы, связанной с подключениями и обменом данными, разберитесь с использованием потоков. Возможно, а это вероятнее всего, вы предусмотрите другую организацию процессов, чем у Фленова и в некоторых примерах открытых кодов.
Интерфейс включает кнопки 1 и 2 выбора режима приложения (клиент/сервер), рядом с каждой из них есть окна 3 и 4 (EditBox), в которое вы должны ввести номер порта. Для режима клиента вы указываете номер порта сервера, к которому хотите подключиться, для режима сервера — номер порта, на котором он запустится.
Тестировать мы все будем на одной и той же машине (компьютере), запустив сразу несколько идентичных приложений.
Стоит сказать, что порты 0—1023 зарезервированы для стандартных служб, поэтому их использование не рекомендуется, лучше выбирать что-нибудь в диапазоне 1024 — 65535.
Поскольку мы будем запускать все приложения на одном локальном компьютере, то укажите в коде IP-адрес: «127.0.0.1».
Кнопка подключения 5 активизирует режим программы. Если это сервер, то сообщение должно появиться в его логе 8 (ListBox). Если клиент, то сообщение должно появиться в окне лога на сервере, тот в свою очередь присылает подтверждение, которое отображается в логе клиента. Для сообщений предусмотрено окно ввода текста 6 (EditBox) и кнопка отправки 7.
Требования:
. В качестве транспортного протокола используется TCP.
. На компьютере можно запустить несколько серверов с разными портами.
. К каждому серверу может подключаться определенное количество клиентов, сервер работает в асинхронном режиме. Если он отправляет сообщение, то его получают все клиенты данного сервера.
. При отключении сервера завершают работу все его клиенты (автоматически закрываются клиентские программы).
. Если один из клиентов послал сообщение, начинающееся с команды, например, destruct, сервер отключается.
Нужно сказать, что выполнение задание займет много времени у начинающих, но вы приобретаете конкретные знания и опыт. А это — лучший приз!
Компьютерная газета. Статья была опубликована в номере 35 за 2009 год в рубрике программирование