Программирование что такое протокол
Протоколы
Протоколы (protocols) – это набор правил и процедур, регулирующих порядок осуществления некоторой связи. В компьютерной среде протоколы – это правила и технические процедуры, позволяющие нескольким компьютерам при объединении в сеть общаться друг с другом.
Компьютер-отправитель в соответствии с протоколом выполняет следующие действия:
Компьютер-получатель в соответствии с протоколом выполняет те же действия, но только в обратном порядке:
И компьютеру-отправителю, и компьютеру-получателю необходимо выполнять каждое действие одинаковым способом, с тем чтобы пришедшие по сети данные совпадали с отправленными. Если, например, два протокола будут по-разному разбивать данные на пакеты и добавлять информацию (о последовательности пакетов, синхронизации и для проверки ошибок), тогда компьютер, использующий один из этих протоколов, не сможет успешно связаться с компьютером, на котором работает другой протокол.
Данные, передаваемые из одной локальной сети в другую по одному из возможных маршрутов, называются маршрутизированными. Протоколы, которые поддерживают передачу данных между сетями по нескольким маршрутам, называются маршрутизируемыми (routable) протоколами. Так как маршрутизируемые протоколы могут использоваться для объединения нескольких локальных сетей в глобальную сеть, их роль постоянно возрастает.
Несколько протоколов, которые работают в сети одновременно, обеспечивают следующие операции с данными:
Работа различных протоколов должна быть скоординирована так чтобы исключить конфликты или незаконченные операции. Этого можно достичь с помощью разбиения на уровни.
Стек протоколов (protocol stack) – это комбинация протоколов. Каждый уровень определяет различные протоколы для управления функциями связи или ее подсистемами. Каждому уровню присущ свой набор правил.
Так же как и уровни в модели 0SI, нижние уровни стека описывают правила взаимодействия оборудования, изготовленного разными производителями. А верхние уровни описывают правила для проведения сеансов связи и интерпретации приложений. Чем выше уровень, тем сложнее становятся решаемые им задачи и связанные с этими задачами протоколы.
В компьютерной промышленности в качестве стандартных моделей протоколов разработано несколько стеков. Вот наиболее важные из них:
Протоколы этих стеков выполняют работу, специфичную для своего уровня. Однако коммуникационные задачи, которые возложены на сеть, приводят к разделению протоколов на три типа:
Как видите, схема расположения этих типов составляет модели OSI.
Прикладные протоколы работают на верхнем уровне модели OSI. Они обеспечивают взаимодействие приложений и обмен данными между ними. К наиболее популярным прикладным протоколам относятся:
Транспортные протоколы поддерживают сеансы связи между компьютерами и гарантируют надежный обмен данных между ними. К популярным транспортным протоколам относятся:
Сетевые протоколы обеспечивают услуги связи. Эти протоколы управляют несколькими типами данных: адресацией, маршрутизацией, проверкой ошибок и запросами на повторную передачу. Сетевые протоколы, кроме того, определяют правила для осуществления связи в конкретных сетевых средах, например, Ethernet или Token Ring. К наиболее популярным сетевым протоколам относятся:
О протоколах передачи данных
В этой статье я хочу поделиться личным, не претендующим на академичность, опытом в работе с различными закрытыми протоколами передачи данными (в основном на прикладном-сеансовом уровнях).
Достаточно часто мне приходиться сопрягаться со специализированным ПО (и железом, хотя в конечном итоге идет сопряжение со вшитым на плату фирмвейром), изготовитель каждого из которых предоставляет свой протокол обмена данными.
Какими свойствами и особенностями обладает хороший, годный грамотный, качественный протокол?
В идеале, протокол должен быть абстрагирован от более нижнего уровня взаимодействия, будь то передача по TCP, UDP, по serial порту, USB, Bluetooth, через цифровой радиосигнал, или даже по голубиной почте. И надо учитывать, что далеко не все из них гарантируют доставку и\или достоверность передающихся данных.
Небольшой дисклеймер: говоря о достоверности данных, я имею ввиду их неискаженность вследствие помех и иных ошибок в среде передачи. В статье я не буду затрагивать темы пласта технологий, связанных с безопасностью в ИТ. Допустим что наши Алиса и Боб могут друг другу доверять, и никакая Ева им помешать не может. (Например у коллег вопрос безопасности решается включением всех территориально разделенных участников взаимодействия в хорошо защищенный VPN, не имеющий в свою очередь доступа наружу)
В большинстве протоколов реализована схема «Вопрос-Ответ». Это можно представить как разговор, в котором на каждую реплику своего собеседника вы реагируете вербально, и в том же смысловом ключе. Таким образом участниками взаимодействия достигается уверенность в том, что их сообщения переданы и адекватно восприняты. Однако эта схема допустима и эффективна не для всех задач: в случаях когда задержка в общении должна быть минимизирована, или ответ на каждую из многочисленных реплик признается избыточным (например для отладочных сообщений), реализуется схема «Старт-Стоп». При получении сообщения на «Старт» ваш собеседник начинает сыпать в вас потоком реплик, и замолкает лишь при слове «Стоп». Сообщения, отправляемые в потоке, обычно имеют инкрементируемый порядковый номер, и если при принятии потока сообщений были проблемы с обработкой\было пропущено одно из них, его можно перезапросить отдельно по этому самому номеру.
Все протоколы можно разделить на две группы, (по представлению данных): символьные и бинарные.
Символьные протоколы, с которыми мне приходилось встречаться, базировались либо на XML, либо на JSON-строках. Из их достоинств можно упомянуть о более простой отладке взаимодействия (вследствие их читаемости), о простоте реализации (наличия готовых парсеров), и пресловутой универсальности.
Теперь о недостатках. Очевидно, что такие протоколы являются крайне избыточными, мизерная доля полезной информации плавает в массивной, неэффективной обёртке. При передаче любой числовой информации приходиться заниматься их конвертацией в строковое представление и обратно. Больным местом является передача бинарных данных (и хорошо, что без них бывает можно обойтись, но в ряде случаев это невозможно). Составители протоколов обычно выкручиваются применением Base64, или даже просто передачей бинарной строки в её hex-овом представлении, по два символа на байт.
Также хочется отметить, что полная спецификация того же XML крайне обширна, и стандартные парсеры, при всей их полноте возможностей, достаточно громоздки и медлительны, поэтому распространена практика, когда отдел или контора в итоге пишет и пользуется собственным парсером.
Конечно, для определенных задач, символьные протоколы являются, если не наиболее эффективным, то по крайней мере вполне приемлимым вариантом, но мы с вами идём дальше.
Теперь бинарные протоколы. Сразу же надо вспомнить о Гулливерских войнах тупоконечников и остроконечников. Лично я симпатизирую big-endian, т.к. не считаю неявную типизацию little-endian «чем-то хорошим», да и в моей среде разработки big-endian является нативным.
Бинарные протоколы (не все, но те, которые я отношу к грамотным) можно разделить на два уровня: уровень контейнера и уровень данных. На плечи первого уровня ложится ответственность за целостность и достоверность передачи данных, а так же за доступность обнаружения сообщения в байтовом потоке, и, само собой, за хранение в себе сообщения уровня данных. Второй уровень должен содержать информацию, ради которой всё сетевое взаимодействие и затевалось, в удобном для обработки формате. Его структура в основном зависит от решаемых задач, но и по нему есть общие рекомендации (о которых ниже).
Размеры сообщений (дискретных пакетов байт, которые можно обрабатывать независимо от предыдущих и последующих принимаемых данных) бывают фиксированными и переменными. Понятно, что с фиксированным размером сообщений всё проще — вычитается, начиная с заголовка (о нём позже), определенное количество байт и отправляется на обработку. Зачастую, для обеспечения гибкости, составители таких протоколов включают в сообщение область фиксированного размера (иногда до 80% от общего объема), зарезервированное под модификации нынешнего протокола. На мой взгляд, это не самый эффективный путь обеспечения гибкости, зато избыточность появляется еще какая.
Рассмотрим сообщения переменной длины.
Тут уже можно подробней поговорить о непременном атрибуте бинарного сообщения в любом протоколе — о заголовке (Это вышеупомянутый уровень контейнера).
Обычно заголовки начинаются с константной части, позволяющей, с определенной вероятностью обнаружить начало сообщения в непрерывном байтовом потоке. Очевидно, что имеется риск появления такой константы в произвольном потоке байт, и, хотя увеличение объема этот риск снижает (я встречал константы вида 0123456789VASIA9876543210), целесообразней использовать проверки на основе подсчета контрольной суммы.
За константой обычно следует номер версии протокола, который дает нам понять, в каком формате должно происходить дальнейшее считывание (и имеем ли мы вообще возможность обработать это сообщение — вдруг такая версия нам неизвестна). Следующая важная часть заголовка: информация о самом содержимом контейнера. Указывается тип содержимого (по факту, тот же номер версии протокола для уровня данных), его длина и контрольная сумма. Имея эту информацию, можно уже без проблем и опасений считать содержимое и приступить к его разбору.
Но не прямо сразу! Заголовок должна заключать контрольная сумма его самого (исключая из расчета конечно саму контрольную сумму) — только так мы можем быть уверены в том, что считали только что не белиберду, а валидный заголовок, за которым следуют предназначенные нам данные. Не совпала контрольная сумма? Придётся искать следующее начало нового заголовка дальше по потоку…
Представим, что мы дошли до этапа, что получили наконец неискаженное сообщение уровня данных. Его структура зависит от той области задач той системы, в которой реализован ваш сетевой обмен, однако в общем виде у сообщения тоже бывает быть свой заголовочек, содержащий информацию о типе сообщения. Можно различить как общую специфику сообщения, (например «Запрос Set», «Утвердительный Ответ на Set», «Отрицательный Ответ на Set», «Запрос Get», «Ответ Get», «Потоковое сообщение»), так и конкретную область применение сообщения. Попробую привести пример с потолка:
Тип запроса: Запрос Set (0x01)
Идентификатор модуля-адресата сообщения: PowerSupplyModule (0x0A)
Идентификатор группы сообщений: UPS Management (0x02)
Идентификатор типа сообщения: Reboot (0x01)
Дальше тело сообщения может содержать информацию об адресе ИБП, который Модуль управления энергообеспечением должен перезагрузить, через сколько секунд это сделать и т.п.
На это сообщение мы рассчитываем получить ответное сообщение с типом запроса «Утвердительный Ответ» и последующими 0x0A0201 в заголовке.
Конечно, такое подробное описание типа сообщения может быть избыточным когда межсетевое взаимодействие не предусматривает большого числа команд, так что формировать структуру сообщения надо исходя из требований ТЗ.
Так же будет полезно, если сообщение с «Отрицательным Ответом» будет содержать код ошибки, из-за которой не удалось ответить на команду утвердительно.
Заканчивая своё повествование, добавлю, что тема взаимодействия приложений весьма обширна и порою холиворна(что по факту означает, что в ней нет технологии «серебряной пули»), и отмечу, что те взгляды, что я излагаю, являются лишь компиляцией из опыта по работе с отечественными и зарубежными коллегами. Спасибо за внимание!
upd.
Имел удовольствие пообщаться с критиком своей статьи, и теперь прихожу к осознанию, что я осветил вопрос со своей если можно так выразиться, «байтолюбской», точки зрения. Конечно, раз идет курс на универсальность обработки хранения и передачи данных, то в таком ключе символьные протоколы (в первую очередь говорю об XML) могут дать фору любым другим решениям. Но относительно попытки повсеместного их применения позволю себе процитировать Вирта:
Инструмент должен соответствовать задаче. Если инструмент не соответствует задаче, нужно придумать новый, который бы ей соответствовал, а не пытаться приспособить уже имеющийся.
Протокольно-ориентированное программирование в Swift 5.1
Протоколы — фундаментальное свойство Swift. Они играют важную роль в стандартных библиотеках Swift и являются обычным способом абстракции кода. Во многом они похожи на интерфейсы в других языках программирования.
В этом руководстве мы представим вам подход к разработке приложений, называемый «протокольно-ориентированным программированием», который стал практически основным в Swift. Это действительно то, что вам необходимо уяснить при изучении Swift!
В этом руководстве вы:
Начинаем
Представьте, что вы разрабатываете игру — гонки. Ваши игроки могут гонять на машинах, мотоциклах и на самолётах. И даже летать на птицах, это же ведь игра, верно? Основное здесь то, что есть дофига всяких «штук», на которых можно гонять, летать и т.п.
Обычный подход при разработке подобных приложений состоит в объектно-ориентированном программировании. В этом случае мы заключаем всю логику в неких базовых классах, от которых в дальнейшем наследуемся. Базовые классы, таким образом, должны содержать внутри логику «вести» и «пилотировать».
Мы начинаем разработку с создания классов для каждого средства передвижения. «Птиц» отложим пока на потом, к ним мы вернёмся чуть позже.
Мы видим, что Car и Motorcycle весьма похожи по функционалу, так что мы создаём базовый класс MotorVehicle. Car и Motorcycle будут наследоваться из MotorVehicle. Таким же образом мы создаем базовый класс Aircraft, от которого создадим класс Plane.
Вы думаете, что всё прекрасно, но — бац! — действие вашей игры происходит в XXX столетии, и некоторые машины уже могут летать.
Итак, у нас случился затык. У Swift нет множественного наследования. Каким образом ваши летающие автомобили смогут наследоваться и от MotorVehicle и от Aircraft? Создавать еще один базовый класс, в котором соединятся обе функциональности? Скорее всего, нет, так как нет ясного и простого способа добиться этого.
И что же спасёт нашу игру в этой ужасной ситуации? На помощь спешит протокольно-ориентированное программирование!
Что такого в протокольно-ориентированном программировании?
Протоколы позволяют группировать похожие методы, функции и свойства применительно к классам, структурам и перечислениям. При этом только классы позволяют использовать наследование от базового класса.
Преимущество протоколов в Swift состоит в том, что объект может соответствовать нескольким протоколам.
Ваш код при при использовании такого метода становиться более модульным. Думайте о протоколах как о строительных блоках функционала. Когда вы добавляете новую функциональность объекту, делая его соответствующим некоему протоколу, вы не делаете совершенно новый объект «с нуля», это было бы слишком долго. Вместо этого, вы добавляете разные строительные блоки до тех пор, пока объект не будет готов.
Переход от базового класса к протоколам решит нашу проблему. С протоколами мы можем создать класс FlyingCar, который соответствует и MotorVehicle и Aircraft. Миленько, да?
Займёмся кодом
Запускаем Xcode, создаём playground, сохраняем как SwiftProtocols.playground, добавляем этот код:
Скомпилируем при помощи Command-Shift-Return, чтобы быть уверенным, что все в порядке.
Здесь мы определяем простой протокол Bird, со свойствами name и canFly. Затем определяем протокол Flyable со свойством airspeedVelocity.
В «допротокольную эпоху» разработчик начал бы с класса Flyable в качестве базового, а затем, используя наследование, определил бы Bird и всё прочее, что может летать.
Но в протокольно-ориентированном программировании всё начинается с протокола. Эта техника позволяет нам инкапсулировать набросок функционала без базового класса.
Как вы сейчас увидите, это делает процесс проектирования типов гораздо гибче.
Определяем тип, соответствующий протоколу
Добавьте этот код внизу playground:
Этот код определяет новую структуру FlappyBird, которая соответствует и протоколу Bird и протоколу Flyable. Её свойство airspeedVelocity — произведение flappyFrequency and flappyAmplitude. Свойство canFly возвращает true.
Теперь добавьте определения еще двух структур:
Penguin это птица, но не может летать. Хорошо, что мы не пользуемся наследованием и не сделали всех птиц Flyable!
При использовании протоколов вы определяете компоненты функционала и делаете все подходящие объекты соответствующими протоколу
Затем мы определяем SwiftBird, но в нашей игре есть несколько разных её версий. Чем больше номер версии, тем больше её airspeedVelocity, которая определена как вычисляемое свойство.
Однако, здесь есть некоторая избыточность. Каждый тип Bird должен определить явно определить свойство canFly, хотя у нас есть определение протокола Flyable. Похоже, что нам нужен способ определить реализацию методов протокола по умолчанию. Что ж, для этого существуют расширения протоколов (protocol extensions).
Расширяем протокол поведением по умолчанию
Расширения протокола позволяют задать поведение протокола по умолчанию. Напишите этот код сразу за определением протокола Bird:
Этот код определяет расширение протокола Bird. В этом расширении определяется, что свойство canFly вернёт true в случае, когда тип соответствует протоколу Flyable. Другими словам, всякой Flyable-птице больше не нужно явно задавать canFly.
Займёмся перечислениями
Перечисления в Swift могут соответствовать протоколам. Добавьте следующее определение перечисления:
Определяя соответствующие свойства, UnladenSwallow соответствует двум протоколам — Bird и Flyable. Такими образом, реализуется определение по умолчанию для canFly.
Переопределяем поведение по умолчанию
Наш тип UnladenSwallow, соответствуя протоколу Bird, автоматически получил реализацию для canFly. Нам, однако, нужно, чтобы UnladenSwallow.unknown возвращала false для canFly.
Добавьте внизу следующий код:
Скомпилируйте playground и проверьте полученные значения с указанными в комментариях выше.
Таким образом мы переопределяем свойства и методы почти так же, как используя виртуальные методы в объектно-ориентированном программировании.
Расширяем протокол
Вы также можете сделать свой собственный протокол соответствующим другому протоколу из стандартной библиотеки Swift и определить поведение по умолчанию. Замените объявление протокола Bird следующим кодом:
Соответствие протоколу CustomStringConvertible означает, что у вашего типа должно быть свойство description. Вместо того, чтобы добавлять это свойство в типе Bird и во всех производных от него, мы определяем расширение протокола CustomStringConvertible, которое будет ассоциировано только с типом Bird.
Наберите UnladenSwallow.african внизу playground. Скомпилируйте и вы увидите “I can fly”.
Протоколы в стандартных библиотеках Swift
Как видите, протоколы — эффективный способ расширять и настраивать типы. В стандартной библиотеке Swift это их свойство также широко применяется.
Добавьте этот код в playground:
Вы наверняка знаете, что выведет этот код, но, возможно, удивитесь использованным здесь типам.
Например, slice — не Array, а ArraySlice. Это специальная «обёртка», которая обеспечивает эффективный способ работы с частями массива. Соответственно, reversedSlice — это ReversedCollection.
К счастью, функция map определена как расширение к протоколу Sequence, которому соответствуют все типы-коллекции. Это позволяет нам применять функцию map как к Array, так и к ReversedCollection и не замечать разницы. Скоро вы воспользуетесь этим полезным приёмом.
На старт
Пока что мы определили несколько типов, соответствующих протоколу Bird. Сейчас же мы добавим нечто совсем другое:
У этого типа нет ничего общего с птицами и полётами. Мы хотим устроить гонку мотоциклистов с пингвинами. Пора выводить эту странную компашку на старт.
Соединяем всё вместе
Чтобы как-то объединить столь разных гонщиков, нам нужен общий протокол для гонок. Мы сможем все это сделать, даже не трогая все созданные нами до этого типы, при помощи замечательной вещи, которая называется ретроактивное моделирование. Просто добавьте это в playground:
Вот что мы тут делаем: сначала определяем протокол Racer. Это всё то, что может участвовать в гонках. Затем мы приводим все наши созданные до этого типы к протоколу Racer. И, наконец, мы создаём Array, который содержит в себе экземпляры каждого нашего типа.
Скомпилируйте playground, чтобы все было в порядке.
Максимальная скорость
Напишем функцию для определения максимальной скорости гонщиков. Добавьте этот код в конце playground:
Здесь мы используем функцию max чтобы найти гонщика с максимальной скоростью и возвращаем её. Если массив пуст, то возвращается 0.0.
Делаем функцию более обобщенной
Предположим, что массив Racers достаточно велик, а нам нужно найти максимальную скорость не во всем массиве, а в какой-то его части. Решение состоит в том, чтобы изменить topSpeed(of:) таким образом, чтобы она принимала в качестве аргумента не конкретно массив, а всё, что соответствует протоколу Sequence.
Заменим нашу реализацию topSpeed(of:) следующим образом:
Теперь наша функция работает с любым типом, отвечающим протоколу Sequence, в том числе и с ArraySlice.
Делаем функцию более «свифтовой»
По секрету: можно сделать ещё лучше. Добавим это в самом низу:
А вот теперь мы расширили сам протокол Sequence функцией topSpeed(). Она применима только в случае, когда Sequence содержит тип Racer.
Компараторы протоколов
Другая особенность протоколов Swift — это то, как вы определяете операторы равенства объектов или их сравнения. Напишем следующее:
Имея протокол Score можно писать код, который обращается со всеми элементами этого типа одним образом. Но если завести вполне определенный тип, такой как RacingScore, то вы не спутаете его с другими производными от протокола Score.
Мы хотим, чтобы очки (scores) можно было сравнивать, чтобы понять, у кого максимальный результат. До Swift 3 разработчикам было нужно писать глобальные функции для определения оператора к протоколу. Теперь же мы можем определить эти статические методы в самой модели. Сделаем это, заменив определения Score и RacingScore следующим образом:
Мы заключили всю логику для RacingScore в одном месте. Протокол Comparable требует опеределить реализацию только для функции «меньше, чем». Все остальные функции сравнения будут реализованы автоматически, на основании созданной нами реализации функции «меньше, чем».
Вносим изменения в объект
До сих пор каждый пример демонстрировал, как добавить функционал. Но что, если мы хотим сделать протокол, который что-то изменяет в объекте? Это можно сделать при помощи mutating методов в нашем протоколе.
Добавьте новый протокол:
Здесь мы определяем протокол, который дает нам возможность жульничать (cheat). Каким образом? Произвольно изменяя содержимое boost.
Теперь создадим расширение SwiftBird, которое соответствует протоколу Cheat:
Здесь мы реализуем функцию boost(_:), увеличивая speedFactor на передаваемую величину. Ключевое слово mutating даёт структуре понять, что одно из её значений будет изменено этой функцией.
Заключение
Здесь вы можете загрузить полный исходный код playground.
Вы узнали о возможностях протокольно-ориентированного программирования, создавая простые протоколы и увеличивая их возможности их при помощи расширений. Используя реализацию по умолчанию, вы даете протоколам соответсвующее «поведение». Почти как с базовыми классами, но только лучше, так как все это применимо также к структурам и перечислениям.
Вы также увидели, что расширение протоколов применимо и к базовым протоколам Swift.
Вы можете также посмотреть прекрасную лекцию на WWDC, посвященную протокольно-ориентированному программированию.
Как и с любой парадигмой программирования, есть опасность увлечься и начать использовать протоколы налево и направо. Здесь интересная заметка о том, что стоит опасаться решений в стиле «серебряная пуля».
Русские Блоги
Протокол TCP / IP и основы сетевого программирования
Существует множество способов локального межпроцессного взаимодействия (IPC), которые можно разделить на следующие четыре категории:
1、TCP/IP
2. Что такое розетка
Поскольку сокет предоставляет интерфейс для набора протоколов TCP / IP,Следовательно, приложения, использующие протокол TCP / IP, обычно используют интерфейс сокета (сокета) UNIX BSD для реализации связи между сетевыми процессами, то есть: процессы в сети обмениваются данными через сокеты.. На данный момент почти все приложения используют сокеты, и сейчас наступил век сети, взаимодействие процессов в сети стало повсеместным, это то, что мы называем «все сокеты».
Теперь, когда известно, что процессы в сети взаимодействуют через сокеты, что такое сокет?
Объяснение сокета: интерфейс сокета составляет один конец соединения, и соединение может быть полностью определено парой интерфейсов сокета.
2.1, представляет процесс в сети
PID процесса можно использовать для однозначной идентификации процесса локально, но это невозможно в сети. Фактически, семейство протоколов TCP / IP помогло нам решить эту проблему. «IP-адрес» сетевого уровня может однозначно идентифицировать хост в сети, а «протокол + порт» транспортного уровня может однозначно идентифицировать приложение. (процесс) в хосте. Таким образом, тройки (IP-адрес, протокол, порт) могут использоваться для идентификации процесса компьютера в сети, а при взаимодействии процессов в сети этот символ может использоваться для завершения взаимодействия между процессами.
2.2, полная связь через сокет
Сцена из жизни. Если вы хотите позвонить другу, сначала наберите номер и подождите, пока друг ответит на него. В то же время, когда друг услышит мелодию звонка и ответит на звонок, когда он свободен, вы и ваш друг установите соединение и вы можете поговорить друг с другом. По окончании обмена положите трубку, чтобы завершить разговор.
Начнем с серверной части. Сервер сначала инициализирует Socket, затем связывается с портом, прослушивает порт, вызывает accept для блокировки и ждет подключения клиента. В это время, если клиент инициализирует Socket, а затем подключается к серверу (соединение), если соединение успешно, то устанавливается соединение между клиентом и сервером. Клиент отправляет запрос данных, сервер получает запрос и обрабатывает запрос, затем отправляет данные ответа клиенту, клиент считывает данные и, наконец, закрывает соединение, и взаимодействие заканчивается.
2.3, TCP, UDP (с установлением соединения и без установления соединения)
И TCP, и UDP построены поверх IP. Следовательно, IP является основой для построения всего набора протоколов TCP / IP. Но IP обеспечивает надежную и надежную услугу без установления соединения. Он принимает пакеты со своего верхнего уровня, инкапсулирует их в IP-пакет, выбирает правильный аппаратный интерфейс для пакета в соответствии с маршрутом и отправляет пакет из этого интерфейса. Как только пакет отправлен, IP больше не заботится о пакете. Как и все протоколы без установления соединения, он больше не запоминает пакет после его отправки.
Эта простота также является основным преимуществом IP. Поскольку он не делает никаких предположений о базовом физическом носителе, он может запускать IP на любом физическом канале, который может переносить пакеты. Например, IP может работать на простых последовательных каналах, Ethernet и Token Ring LAN, X.25, WAN с использованием ATM (асинхронный режим передачи, асинхронный режим передачи), CDPD (сотовые цифровые пакетные данные, беспроводные сотовые цифровые пакетные данные) сети и многие другие сети. Хотя между этими сетевыми технологиями есть большие различия, IP обращается с ними одинаково и не делает никаких предположений о них, кроме того, что они могут пересылать пакеты. Этот механизм подразумевает глубокий смысл. IP может работать в любой сети, которая может передавать пакеты, так же как и весь набор протоколов TCP / IP.
Расширение TCP через IP
Теперь давайте посмотрим, как TCP использует этот простой сервис без установления соединения для предоставления надежных сервисов с установлением соединения.Пакеты TCP называются сегментами и отправляются в дейтаграммах IP, поэтому невозможно предположить, что эти пакеты прибудут к месту назначения, не говоря уже о том, чтобы гарантировать, что пакеты прибудут в исходном порядке без повреждений.
Чтобы обеспечить такую надежность, TCP добавляет к основным IP-сервисам три функции:
Расширение UDP через IP
С другой стороны, UDP предоставляет ненадежную службу без установления соединения для программистов, пишущих приложения. Фактически, UDP добавляет только две функции к базовому IP-протоколу.
Аналогия TCP и UDP
Для TCP и UCP стандартная аналогия: использование протокола без установления соединения похоже на отправку письма, а использование протокола, ориентированного на установление соединения, похоже на телефонный звонок.
А теперь давайте посмотрим, что происходит, когда мы звоним другу, а не отправляем письмо.
подводить итоги
Сетевой адрес в TCP-соединении можно рассматривать как телефонный номер офисного коммутатора, а номер порта можно рассматривать как добавочный номер конкретного телефона, вызываемого в офисе. Таким же образом сетевой адрес UDP можно рассматривать как адрес многоквартирного дома, а номер порта можно рассматривать как личный почтовый ящик в холле многоквартирного дома.
3. Основные операции розетки
Поскольку сокет является реализацией режима «открытие-запись / чтение-закрытие», сокет предоставляет функциональные интерфейсы, соответствующие этим операциям. Давайте возьмем TCP в качестве примера, чтобы представить несколько основных функций интерфейса сокетов.
3.1, функция socket ()
socket () используется для создания дескриптора сокета (дескриптора сокета), который однозначно идентифицирует сокет.
Функция сокета соответствует операции открытия обычных файлов. Операция открытия обычного файла возвращает дескриптор файла, и этот дескриптор сокета совпадает с дескриптором файла, и последующие операции будут использовать его и использовать в качестве параметра для выполнения некоторых операций чтения и записи.
Так же, как вы можете передавать различные значения параметров в fopen для открытия разных файлов. При создании сокета вы также можете указать разные параметры для создания разных дескрипторов сокета. Три параметра функции сокета:
Примечание: Нельзя произвольно комбинировать вышеуказанный тип и протокол.Например, SOCK_STREAM нельзя комбинировать с IPPROTO_UDP. Когда протокол равен 0, автоматически выбирается протокол по умолчанию, соответствующий типу типа.
Когда мы вызываем socket для создания сокета, возвращаемое слово описания сокета существует в пространстве семейства протоколов (семейство адресов, AF_XXX), но не имеет конкретного адреса. Если вы хотите назначить ему адрес, вы должны вызвать функцию bind (), в противном случае система автоматически случайным образом назначит порт, когда вы вызываете connect () и listen ().
3.2, функция bind ()
Функция bind () назначает сокету конкретный адрес из семейства адресов. Например, AF_INET и AF_INET6 должны назначить сокету комбинацию адреса ipv4 или ipv6 и номера порта.
Три параметра функции:
Примечание: структура адреса протокола второго параметра является частным адресом.—— При создании сокета отличаться в соответствии с семейством протоколов адресов, например:
Домен Unix соответствует:
Обычно сервер привязан к общеизвестному адресу (например, IP-адрес + номер порта), когда он запускается для предоставления услуг, и клиент может подключаться к серверу через него; клиенту не нужно указывать, операционная система автоматически назначит номер порта и собственную комбинацию IP-адресов. Вот почему обычно сервер вызывает bind () перед прослушиванием, но клиент не вызывает его. Вместо этого система случайным образом генерирует его при connect ().
Сетевой порядок байтов и порядок байтов хоста
Следовательно: При привязке адреса к сокету, пожалуйста, сначала преобразуйте порядок байтов хоста в сетевой порядок байтов вместо того, чтобы предполагать, что порядок байтов хоста такой же, как сетевой порядок байтов с использованием Big-Endian. Из-за этой проблемы были совершены кровавые преступления! Из-за этой проблемы в коде проекта компании она вызвала множество необъяснимых проблем, поэтому, пожалуйста, помните, что не следует делать никаких предположений о порядке байтов хоста, не забудьте преобразовать его в сетевой порядок байтов и назначить его сокету.
3.3, функции listen (), connect ()
Если в качестве сервера после вызова socket () и bind () будет вызываться listen () для мониторинга сокета. Если в это время клиент вызывает connect () для отправки запроса на соединение, сервер получит запрос.
3.4, функция accept ()
После того как TCP-сервер по очереди вызовет socket (), bind () и listen (), он будет отслеживать указанный адрес сокета. После того, как TCP-клиент вызывает по очереди socket () и connect (), он отправляет запрос на соединение на TCP-сервер. После того, как TCP-сервер прослушивает запрос, он вызывает функцию accept () для получения запроса, чтобы соединение было установлено. После этого вы можете запускать операции сетевого ввода-вывода, то есть операции чтения и записи, аналогичные обычным файлам.
нота:
3.5, read (), write () и другие функции
На этом этапе сервер и клиент установили соединение. Сетевой ввод / вывод может быть вызван для операций чтения и записи, то есть реализуется связь между различными процессами в сети!
Существуют следующие группы сетевых операций ввода-вывода:
Рекомендуется использовать функции recvmsg () / sendmsg (), эти две функции являются наиболее распространенными функциями ввода-вывода.
Мы должны разбираться с этим в зависимости от типа ошибки. Если это ошибка EINTR, это означает, что при записи произошла ошибка прерывания. Если это EPIPE, проблема с сетевым подключением (другая сторона закрыла подключение).
3.6, функция close ()
После того, как сервер установит соединение с клиентом, будут выполнены некоторые операции чтения и записи. После завершения операций чтения и записи соответствующий дескриптор сокета должен быть закрыт. Это похоже на вызов fclose для закрытия открытого файла после завершения операции завершено.
По умолчанию закрытие сокета TCP помечает сокет как закрытый, а затем немедленно возвращается к вызывающему процессу. Слово описания больше не может использоваться вызывающим процессом, то есть оно больше не может использоваться в качестве первого параметра чтения или записи.
По умолчанию close () немедленно отправляет в сеть пакет FIN, независимо от того, есть ли данные в выходном буфере. Это означает, что вызов close () приведет к потере данных в выходном буфере ядра.
Примечание: операция закрытия только делает счетчик ссылок соответствующего дескриптора сокета 1. Только когда счетчик ссылок равен 0, клиент TCP запускается для отправки запроса на завершение соединения на сервер.
4. Установка и закрытие розетки
4.1 Подробное объяснение установления TCP-соединения с трехсторонним рукопожатием в сокете
Клиент должен использовать функцию connect (), чтобы установить соединение с сервером перед отправкой и получением данных. Целью установления соединения является обеспечение правильности IP-адреса, порта и физического соединения, а также открытие канала для передачи данных.
Мы знаем, что TCP устанавливает «трехстороннее рукопожатие», то есть происходит обмен тремя пакетами. Его можно сравнить со следующим диалогом:
Трехсторонний процесс установления связи и передача состояния, API сокетов
подводить итоги:
4.2, передача данных TCP
После установления соединения два хоста могут передавать данные друг другу.
4.2.1, 200 байт передаются без потери данных
Сначала хост A отправляет 100 байтов данных через один пакет, а номер Seq пакета устанавливается на 1200. Чтобы подтвердить это, хост B отправляет пакет ACK хосту A и устанавливает номер Ack равным 1301.
Чтобы гарантировать точную доставку данных, целевая машина должна немедленно вернуть пакет ACK после получения пакета данных (включая пакет SYN, пакет FIN, обычный пакет данных и т. Д.), Чтобы отправитель мог подтвердить успех передача информации.
Номер подтверждения = номер Seq + количество переданных байтов + 1
Так же, как и в протоколе трехстороннего рукопожатия, добавление 1 в конце означает, что другой стороне будет передаваться номер Seq.
4.2.2, 200 байтов пакета данных / пакета подтверждения потеряны и переданы
На приведенном выше рисунке показано, что 100 байтов данных передаются на хост B через пакет Seq 1301, но в середине произошла ошибка, и хост B не получил ее. По прошествии некоторого времени хост A все еще не получил подтверждение ACK для Seq 1301, поэтому он пытается повторно передать данные.
Чтобы завершить повторную передачу пакетов данных, сокет TCP будет запускать таймер каждый раз при отправке пакета данных. Если пакет ACK от целевой машины не будет получен в течение определенного периода времени, таймер истечет, и данные пакет будет передан повторно.
На приведенном выше рисунке показан случай потери пакета данных. Также будут случаи потери пакета ACK, который также будет передан повторно.
Тайм-аут повторной передачи (RTO, тайм-аут повторной передачи)
Время приема-передачи (RTT, Round-Trip Time) представляет собой общую задержку от начала отправки данных отправителю до тех пор, пока отправитель не получит пакет подтверждения ACK от получателя (получатель немедленно подтверждает после получения данных).
Количество ретрансляций
Количество повторных передач TCP-пакетов зависит от настроек системы. В некоторых системах пакет данных будет повторно передан только 3 раза.Если подтверждение ACK пакета данных не получено после 3 повторных передач, попытки повторной передачи предприниматься не будут. Однако некоторые бизнес-системы с высокими требованиями будут постоянно повторно передавать потерянные пакеты данных, чтобы обеспечить нормальное взаимодействие бизнес-данных в максимально возможной степени.
Наконец, следует отметить, что отправитель очистит данные в выходном буфере только после получения пакета подтверждения ACK от другой стороны.
4.3 Подробное объяснение четырех волн TCP в сокете для освобождения соединения
Установление соединения очень важно, это предпосылка для правильной передачи данных;Отключение также важно, оно позволяет компьютеру высвободить ресурсы, которые больше не используются.. Если соединение не может быть отключено обычным образом, это не только вызовет ошибки передачи данных, но также приведет к тому, что сокет не будет закрыт и продолжит занимать ресурсы. Если количество одновременных операций велико, нагрузка на сервер будет вызывать беспокойство.
Четыре волны разрыва связи можно сравнить со следующим диалогом:
Поскольку TCP-соединение является полнодуплексным, каждое направление должно быть закрыто отдельно. Этот принцип заключается в том, что когда одна из сторон завершает задачу отправки данных, она отправляет FIN, чтобы завершить соединение в этом направлении. Получение FIN означает только отсутствие потоков данных в в этом направлении, то есть данные больше не будут приниматься (read / recv вернет 0), но данные все еще могут быть отправлены по этому TCP-соединению, пока FIN также не будет отправлен в этом направлении.
Четырехволновый процесс и переход между состояниями
подводить итоги
4.4, прочие вопросы
Это связано с тем, что в состоянии LISTEN сервер получает сообщение SYN для установления запроса на соединение,Клиент помещает ACK и SYN в сообщение и отправляет его клиенту.. Когда соединение закрывается, при получении сообщения FIN от другой стороны это означает только то, что другая сторона больше не отправляет данные, но все еще может получать данные. Вы не можете отправить все данные другой стороне, поэтому вы можете немедленно закрыть или отправить некоторые. После того, как данные будут отправлены другой стороне, отправьте другой стороне сообщение FIN, чтобы указать, что соединение сейчас закрыто. Следовательно,Собственные ACK и FIN обычно отправляются отдельно.
4.4.2. Описание статуса TIME_WAIT
Клиент переходит в состояние TIME_WAIT после отправки пакета ACK в последний раз вместо прямого перехода в состояние CLOSED для закрытия соединения. Почему?
Когда клиент отправляет обратно пакет ACK серверу в последний раз, сервер может не получить его из-за сетевых проблем, и сервер снова отправит пакет FIN. Если клиент полностью закрывает соединение в это время, сервер в любом случае не получит ACK.Клиенту необходимо подождать некоторое время и подтвердить, что другая сторона получает пакет ACK, прежде чем перейти в состояние ЗАКРЫТО. Итак, как долго тебе нужно ждать?
У пакетов данных есть время, чтобы жить в сети. По истечении этого времени они будут отброшены до достижения целевого хоста, а исходный хост будет уведомлен. Это называется
Максимальное время жизни сегмента (MSL, максимальное время жизни сегмента)
5. Доменное имя
12 арабских цифр трудно запомнить. Проще использовать имя.
Когда вы набираете что-то вродеhttp://www.w3school.com.cn Для такого доменного имени программа DNS переводит доменное имя в число.
Во всем мире к Интернету подключено большое количество DNS-серверов. DNS-сервер отвечает за преобразование доменных имен в TCP / IP-адреса, а также отвечает за обновление систем друг друга новой информацией о доменных именах.
Когда новое доменное имя регистрируется вместе с его TCP / IP-адресом, DNS-серверы по всему миру обновляют эту информацию.