К содержанию

Что такое сокет?

Вы постоянно слышите разговоры о каких-то «сокетах» и, наверно, вам интересно, что же это такое. В общем, изначально сокеты — это способ общения программ друг с другом, используя файловые дескрипторы Unix.

Что?

Ок — возможно, вы слышали от какого-нибуть Unix-хакера фразу типа «господи, всё, что есть в Unix — файлы!» Этот человек, возможно, имел в виду, что программы в Unix при абсолютно любом вводе-выводе читают или пишут в файловый дескриптор. Дескриптор файла — это простое целое число, связанное операционной системой с открытым файлов. Но (и в этом заключается ловушка) файлом может быть и сетевое подключение, и FIFO, и пайпы, и терминал, и реальный файл на диске, и просто что угодно другое. Всё в UNIX — это файл! Итак, просто поверьте, что собираясь общаться с другой программой через интернет, вам придется делать это через дескриптор файла.

«Эй, умник, а откуда мне взять этот дескриптор файла для работы в сети?» Отвечу.
Вы совершаете системный вызов socket(). Он возвращает дескриптор сокета, и вы общаетесь через него с помощью системных вызовов send() и recv() (man send, man recv).

«Но, эй!» могли бы вы воскликнуть. «Если это дескриптор файла, почему я не могу использовать простые функции read() и write(), чтобы общаться через него?». Ответ прост: «Вы можете!». Немного развернутый ответ: «Вы можете, но send() и recv() предлагают гораздо больший контроль над передачей ваших данных.»

Что дальше? Как насчет этого: бывают разные виды сокетов. Есть DARPA инернет-адреса (Сокеты интернет), CCITT X.25 адреса (X.25 сокеты, которые вам не нужны), и, вероятно, многие другие в зависимости от особенностей вашей ОС. Этот документ описывает только первые, Интернет-Сокеты.

Два типа интернет-сокетов

Что? Есть два типа интернет сокетов? Да. Ну ладно, нет, я вру. Есть больше, но я не хочу вас пугать. Есть ещё raw-сокеты, очень мощная штука, вам стоит взглянуть на них.

Ну ладно. Какие два типа? Один из них — «потоковый сокет», второй — «сокет дейтаграмм», в дальнейшем они будут называться «SOCK_STREAM» и «SOCK_DGRAM» соответственно. Дейтаграммные сокеты иногда называют «сокетами без соединения» (хотя они могут и connect()`иться, если вам этого действительно захочется. См. connect() ниже.)

Потоковые сокеты обеспечивают надёжность своей двусторонней системой коммуникации. Если вы отправите в сокет два элемента в порядке «1, 2», они и «собеседнику» придут в том же порядке — «1, 2». Кроме того, обеспечивается защита от ошибок.

Что использует потоковые сокеты? Ну, вы наверно слышали о программе Telnet, да? Телнет использует потоковый сокет. Все символы, которые вы печатаете, должны прибыть на другой конец в том же порядке, верно? Кроме того, браузеры используют протокол HTTP, который в свою очередь использует потоковые сокеты для получения страниц. Если вы зайдёте телнетом на любой сайт, на порт 80 и наберёте что-то вроде «GET / HTTP/1.0» и нажмете ввод два раза, на вас свалится куча HTML 😉

Как потоковые сокеты достигают высокого уровня качества передачи данных? Они используют протокол под названием «The Transmission Control Protocol», иначе — «TCP». TCP гарантирует, что ваши данные передаются последовательно и без ошибок. Возможно, ранее вы слышали о TCP как о половине от «TCP/IP», где IP — это «Internet Protocol». IP имеет дело в первую очередь с маршрутизацей в Интернете и сам по себе не отвечает за целостность данных.

Круто. А что насчёт дейтаграммных сокетов? Почему они называются без-соединительными? В чем тут дело? Почему они ненадежны?
Ну, вот некоторые факты: если вы посылаете дейтаграмму, она может дойти. А может и не дойти. Но если уж приходит, то данные внутри пакета будут без ошибок.

Дейтаграммные сокеты также используют IP для роутинга, но не используют TCP; они используют «User Datagram Protocol», или «UDP».

Почему UDP не устанавливает соединения? Потому что вам не нужно держать открытое соединение с потоковыми сокетами. Вы просто строите пакет, формируете IP-заголовок с информацией о получателе, и посылаете пакет наружу. Устанавливать соединение нет необходимости. UDP как правило используется либо там, где стек TCP недоступен, либо там, где один-другой пропущеный пакет не приводит к концу света. Примеры приложений: TFTP (trivial file transfer protocol, младшый брат FTP), dhcpcd (DHCP клиент), сетевые игры, потоковое аудио, видео конференции и т.д.

«Подождите минутку! TFTP и DHCPcd используются для передачи бинарных данных с одного хоста на другой! Данные не могут быть потеряны, если вы хотите нормально с ними работать! Что это за темная магия?»

Нуу, мой человеческий друг, TFTP и подобные программы обычно строят свой собственный протокол поверх UDP. Например, TFTP протокол гласит, что для каждого принятого пакета получатель должен отправить обратно пакет, говорящий «я получил его!» («ACK»-пакет). Если отправитель исходного пакета не получает ответ, скажем, в течение 5 секунд, он отправит пакет повторно, пока, наконец, не получит ACK. Подобные процедуры очень важны для реализации надёжных приложений, использующих SOCK_DGRAM.

Для приложений, не требующих такой надёжности — игры, аудио или видео, вы просто игнорируете потерянные пакеты или, возможно, пытаетесь как-то их компенсировать. (Игроки в quake обычно называют это явление «проклятый лаг», и «проклятый» — это ещё крайне мягкое высказывание).

Зачем вам может понадобиться использовать ненадежный базовый протокол? По двум причинам: скорость и скорость. Этот способ гораздо быстрее, выстрелил-и-забыл, чем постоянное слежение за тем, всё ли благополучно прибыло получателю. Если вы отправляете сообщение в чате, TCP великолепен, но если вы шлёте 40 позиционных обновлений персонажа в секунду, может быть, не так и важно, если один или два из них потеряются, и UDP тут будет неплохим выбором.

К содержанию

Теория сетей и низкие уровни

Поскольку я только что упоминал слои протоколов, пришло время поговорить о том, как на самом деле работает сеть, и показать примеры того, как построены пакеты SOCK_DGRAM. На самом деле вы можете пропустить этот раздел, но он является неплохим теоретическим подспорьем.

Эй, детишки, настало время поговорить об инкапсуляции данных! Это очень-очень важная вещь. Это настолько важно, что вам стоит выучить это наизусть.
В основном суть такова: пакет родился; пакет завёрнут («инкапсулирован») в заголовок первым протоколом (скажем, протоколом TFTP), затем всё это (включая хидер TFTP) инкапсулируется вновь следующим протоколом (скажем, UDP), затем снова — следующим (например, IP), и наконец финальным, физическим протоколом (скажем, Ethernet).

Когда другой компьютер получает пакет, оборудование (сетевая карта) исключает Ethernet-заголовок (разворачивает пакет), ядро ОС исключает заголовки IP и UDP, программа TFTP исключает заголовок TFTP, и наконец мы получаем голые данные.

Теперь наконец можно поговорить о печально известной модели OSI — многоуровневой модели сети. Эта модель описывает систему сетевой функциональности, которая имеет много преимуществ по сравнению с другими моделями. Например, вы можете написать в своей программе как сокеты, которые шлют данные не заботясь о том, как физически передаются данные (серийный порт, эзернет, модем и т.д.), так как программы на более низких уровнях (ОС, драйверы) делают за вас всю работу, и представляют её прозрачно для программиста.

Собственно, вот все уровни полномасштабной модели:

  • Прикладной
  • Представительский
  • Сеансовый
  • Транспортный
  • Сетевой
  • Канальный
  • Аппаратный (физический)

Физический уровень — это оборудование; ком-порт, сетевая карта, модем и т.д. Прикладной слой — дальше всех отстоит от физического. Это то место, где пользователь взаимодействует с сетью.

Для нас эта модель слишком общая и обширная. Сетевая модель, которую можем использовать мы, может выглядеть так:

  • Уровень приложений (Telnet, FTP и т.д.)
  • Транспортный протокол хост-хост (TCP, UDP)
  • Интернет-уровень (IP и маршрутизация)
  • Уровень доступа к сети (Ethernet, Wi-Fi или что угодно)

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

Видите, как много работы заключается в создании одного простого пакета? Офигеть! И все эти заголовки пакетов вы должны самостоятельно набирать в блокноте! Шучу. Всё, что вам нужно сделать в случае потоковых сокетов — это послать (send()) данные наружу. Ядро ОС построит TCP и IP хидеры, а оборудование возьмет на себя уровень доступа к сети. Ах, я люблю современные технологии.

На этом наш краткий экскурс в теорию сетей завершен. Ах да, я забыл вам сказать: всё, что я хотел вам сказать о маршрутизации: ничего! Да-да, я ничего не буду говорить об этом. О таблице маршрутизации за вас позаботятся ОС и IP-протокол. Если вам действительно интересно, почитайте документацию в интернете, её море.

К содержанию