
Лекция 8
Сокеты
-
Введение 2. Потоковые сокеты
1.Введение
Идея механизма сокетов состоит в рассмотрении сетевых подключений как файлов, и, таким образом, программы могут читать из сокета или писать в сокет, как они делают это с файлом. Слово Socket в переводе на русский язык означает "гнездо". Это название образовалось по аналогии с гнёздами (разъемами) на аппаратуре, с которыми стыкуются разъемы. Сокет представляет собой программную конструкцию (объект), которая определяет конечную точку соединения.
Любой компьютер, сервер, подключённый к локальной или глобальной сети, назывется хостом (host- хозяин, принимающий гостей). Клиент/серверная модель представляет собой архитектуру разработки приложений, предназначенную для отделения уровня представления данных от их обработки и хранения.
Клиентские хосты запрашивают обслуживание, и хост сервера обеспечивает обслуживание этих запросов. Запрос передается от клиен-та на сервер через сеть, обработка выполняется на сервере и скрыта от клиента. При этом один сервер может обслуживать несколько клиентов. На Рис. 1 представлена клиент/серверная архитектура, обслуживающая несколько клиентов.
Рисунок 1 - Несколько клиентов, получающих доступ к серверу.
Сервер и клиент не обязательно являются отдельными компонентами оборудования, они могут быть реализованы программами, работающими на одной или различных машинах.
Серверная часть клиент/серверного приложения управляет ресурсами, распределенными среди нескольких пользователей, которые получают доступ к серверу вместе с другими клиентами.
В восьмидесятых годах американское правительственное агентство по поддержке исследовательских проектов (ARPA), финансировало реализацию протоколов TCP/IP для UNIX в Калифорнийском университете в г. Беркли. В ходе этого проекта группа исследователей-программистов разработала интерфейс прикладного программирования для сетевых приложений TCP/IP (TCP/IP API). Этот интерфейс был назван сокетами TCP/IP (TCP/IP sockets). Так как изначально разработчиками интерфейса сокетов были исследователи университета в г. Беркли, этот интерфейс часто называется сокетами Беркли (Berkeley sockets) или сокетами в стиле Беркли. Сокет идентифицирует конечную точку сети, точку взаимодействия.
Интерфейс сокетов — это API для сетей TCP/IP. Другими словами, он описывает набор программных функций или процедур, позволяющий разрабатывать приложения для использования в сетях TCP/IP.
Сокет, как точка взаимодействия однозначно характеризуется парой (ip-адрес, номер порта). Где ip-адрес – характеризует положение хоста клиента или сервера в сети, а номер порта является порядковым номером вычислительного процесса на хосте. Таким образом, полный адрес удаленного процесса или промежуточного объекта для конкретного способа связи с точки зрения операционных систем определяется парой: <числовой адрес компьютера в сети, локальный адрес(номер порта)>.
Интерфейс сокетов Беркли проще понять, имея хотя бы общее представление о системе ввода-вывода в UNIX. Разработчики сокетов Беркли стремились реализовать протоколы TCP/IP в рамках операционной среды UNIX. При этом они располагали уже имеющимся в UNIX набором вызовов системных функций. В результате, интерфейс сокетов использовал те же системные вызовы, что и остальные программы. Он оказался встроенным в саму операционную систему.
Системные вызовы ввода-вывода в UNIX выглядят как последовательный цикл, состоящий из операций типа открыть-считать-записать-закрыть. Файл, перед тем как читать из него, должен быть открыт. Далее над ним выполняются операции по считыванию/записи. По окончании операций файл закрывается. Программисты в UNIX могут не знать различий между файлами и внешними устройствами. Одни и те же системные вызовы используются по отношению к принтеру, модему, дисплею и файлам. Внешние устройства и файлы отображены в UNIX на одну файловую систему. Чтобы открыть файл или получить доступ к устройству, например стриммеру, программист должен вызвать одну и ту же системную функцию. В ответ на системный вызов UNIX возвращает так называемый дескриптор файла (file descriptor); его иногда называют указателем (file handler). Дескриптор файла указывает на внутрисистемную таблицу, описывающую открытый файл или устройство. Для файла в таблице могут приводиться его имя, размер, дата создания и т.п. Дескриптор файла в UNIX может указывать на файл, внешнее устройство или любой другой объект, позволяющий выполнять над ним системные функции ввода или вывода.
На первых порах разработки интерфейса сокетов исследователи пытались заставить сетевой ввод-вывод функционировать также, как и любой другой ввод-вывод UNIX. Другими словами, они хотели, чтобы интерфейс сокетов считывал и записывал данные в уже известном нам цикле открыть-считать-записать-закрыть. То есть, устанавливая сетевое соединение, программа бы открывала его, затем считывала и записывала в него данные. Наконец, соединение закрывалось бы. С точки зрения UNIX такой подход сделал бы сетевой ввод-вывод точно таким же, как и все остальные операции по вводу-выводу данных, значительно облегчив интеграцию сокетов в операционную систему.
В процессе разработки исследователи выяснили, что сетевой ввод-вывод значительно сложнее, чем ввод-вывод для любых других устройств. Сетевые программы, как правило, создаются по модели клиент-сервер. Под сервером понимается компонент (программа), которая предоставляет некоторый ресурс (например, вычислительный). Под клиентом понимается компонент, который использует ресурс для решения собственных задач, но использует его не непосредственно, а по средством обращения к серверу. Ресурсы, доступ к которым осуществляется посредством сервера можно разделить на четыре категории:
• общие - дисковые ресурсы;
• ограниченные — принтеры, модемы, дисковые массивы;
• совместно используемые - базы данных, программные проекты, документация;
• делегируемые - удаленные программы, распределенные запросы.
Разработчики сокетов могли просто реализовать такой API для системы ввода-вывода UNIX, в котором программист мог бы создать программу-клиент, активно устанавливающую сетевое соединение. Однако этот же API был бы должен позволить создавать и программы-серверы, пассивно ждущие, пока к ним кто-нибудь не обратится. Поскольку обычная система ввода-вывода UNIX попросту не умеет пассивно вводить и выводить данные, разработчики были вынуждены создать набор новых функций для обработки пассивных операций ввода-вывода. Было обнаружено, что стандартные функции ввода-вывода UNIX также плохо умеют устанавливать соединения. Как правило, они пользуются фиксированным адресом файла и устройства для обращения к нему. То есть адрес файла или устройства для каждого компьютера - постоянная величина. Соединение (или путь) к файлу или устройству доступно на протяжении всего цикла запись-считывание - то есть до тех пор, пока программа не закроет соединение.
Фиксированный адрес хорош, если протокол передачи данных ориентирован на соединение. Для не ориентированных на соединение протоколов фиксированный адрес представляет проблему. Например, в датаграмме, переданной IP, присутствует адрес компьютера-получателя (IP-адрес), однако протокол не устанавливает предварительно никакого соединения. В системе ввода-вывода UNIX отсутствуют какие-либо возможности для этого. Одним словом, когда разработчики сокетов приступили к работе, система ввода-вывода UNIX предоставляла возможность только считывать-записывать данные протоколам, передающим потоки байтов. Две вышеперечисленные и некоторые другие, менее очевидные проблемы привели к тому, что разработчики отказались от внесения модификаций в существующую систему ввода-вывода UNIX, а вместо этого создали новый API (набор функций).
Сокет можно рассматривать, как конечный пункт передачи данных по сети. Сетевое соединение - это процесс передачи данных по сети между двумя компьютерами или процессами. Другими словами, когда программы используют сокет, для них он является абстракцией, представляющий одно из окончаний сетевого соединения. Для установления соединения в абстрактной модели сокетов необходимо, чтобы каждая из сетевых программ имела свой собственный сокет. Связь между двумя сокетами может быть ориентирована на соединение, а может быть и нет. Несмотря на то, что разработчики модифицировали системный код UNIX, интерфейс сокетов по-прежнему использует концепцию ввода-вывода данных UNIX. То есть сетевая модель интерфейса сокетов использует цикл открыть-считать-записать-закрыть. Чтобы открыть или создать файл в UNIX (и в большинстве других ОС), вы должны указать его описание (например, имя файла и то, как вы будете его использовать: записывать или считывать). Затем вы запрашиваете у операционной системы дескриптор файла, соответствующий вашему описанию. Не существует каких-либо ограничений на то, когда запрашивать дескриптор. Как только вам нужен файл, запрашивайте его дескриптор. В один и тот же момент времени может быть открыто несколько устройств или файлов. В любом случае операционная система возвращает дескриптор (обычно это целое число), однозначно соответствующий указанному файлу или устройству. Интерфейс сокетов работает точно также. Когда программе нужен сокет, она формирует его характеристики и обращается к API, запрашивая у сетевого программного обеспечения его дескриптор.
Однако есть и отличия. Тогда как дескриптор файла указывает на определенный файл (уже существующий или только что созданный) или устройство, дескриптор сокета не содержит каких-либо определенных адресов или пунктов назначения сетевого соединения. Программы, работающие с сокетами, сначала образуют сокет и только потом соединяют его с точкой назначения на другом конце сетевого соединения. Если бы файловый ввод-вывод состоял из этих же шагов, приложение сначала получало бы дескриптор файла, а затем привязывало бы его к имени определенного файла на жестком диске.
Создавая программу TCP/IP, необходимо иметь возможность пользоваться как ориентированными (TCP), так и не ориентированными на соединение (UDP) протоколами. Интерфейс сокетов позволяет программам использовать оба этих типа протоколов. Однако процессы создания сокета и соединения сокета с компьютером-получателем происходят раздельно.
2. Основы программирования сокетов. Потоковые сокеты
Механизм сокетов создавался в начале-середине 80-х годов, когда объектно-ориентированный подход в программировании еще не получил такого широкого распространения. Изложение производится на основе базовой библиотеки сокетов для языка C/C++(Berkley Sockets), использующий процедурный подход, в дальнейшем рассматривается реализация объектно-ориентированной библиотеки сокетов Java. В первую очередь рассматриваются потоковые TCP-сокеты.
Как уже было замечено, существуют два механизма, предназначенных для сетевого взаимодействия программ, - это сокеты датаграмм, которые используют пользовательский датаграммный протокол (User Datagram Protocol) (UDP) без установления соединения, и сокеты, используюющие протокол управления передачей / Межсетевой протокол (Transmission Control Protocol/Internet Protocol) (TCP/IP), устанавливающий соединение.
Датаграмма - пакет данных, отправленный по сети, прибытие которого, время прибытия и содержание не гарантировано. Не гарантируется также и порядок доставки пакетов. При передаче пакета UDP по какому-либо адресу нет никакой гарантии того, что он будет принят, а также, что по этому адресу вообще существует потребитель пакетов. Аналогично, когда вы получаете датаграмму, у вас нет никаких гарантий, что она не была повреждена в пути следования или что отправитель ожидает подтверждения получения датаграммы. Использование UDP может привести к потере или к дублированию пакетов, что приводит к дополнительным проблемам, связанным с проверкой ошибок и обеспечением надежности передачи данных. Если вам необходимо добиться оптимальной производительности, и вы готовы сократить затраты на проверку целостности информации, пакеты UDP могут оказаться весьма полезными.
При использовании потоковых сокетов, программа устанавливает соединение с другим сокетом и, пока соединение установлено, поток данных протекает между программами, и говорят, что потоковые сокеты обеспечивают обслуживание на основе установления соединения. TCP/IP является потоковым протоколом на основе установления двунаправленных соединений точка-точка между узлами Интернет, и взаимодействие между компьютерами по этому протоколу предназначено для реализации надежной передачи данных. Все данные, отправленные по каналу передачи, получаются в том же порядке, в котором они передавались. В отличие от датаграммных сокетов, сокеты TCP/IP реализуют высоконадежные устойчивые соединения между клиентом и сервером.
Функционирование клиента и сервера по сокету можно проиллюстрировать на примере телефонной трубки, которая имеет два основных элемента: микрофон (передатчик) и динамик (приемник). А телефонный номер, по сути, представляет собой уникальный адрес трубки. У сокета имеются такие же два канала: один для прослушивания, а другой для передачи (подобно каналам ввода-вывода в файловой системе). Клиент (звонящий) подключается к серверу (абоненту), чтобы начать сетевой разговор. Каждый участник разговора предлагает несколько стандартных, заранее известных сервисов (см. файл /etc/ services), например телефон, по которому можно узнать правильное время.
Клиентская программа должна предпринять несколько действий для установления соединения с другим компьютером или сервером. Причем эти действия следует выполнять в определенном порядке. Конечно, возникает вопрос: "А почему нельзя все упростить?" Дело в том, что на каждом из этапов программа может задавать различные опции. Но не пугайтесь: не все действия являются обязательными. Если пропустить некоторые из них, операционная система воспользуется установками по умолчанию.
Базовая последовательность действий имеет такой вид: создание сокета, поиск адресата, организация канала связи с другой программой и разрыв соединения. Ниже в графическом виде представлены действия, которые должен предпринять клиент при подключении к серверу (Рис. 2).
Рисунок 2 – Последовательность действий клиента при подключении к серверу
Опишем каждый из этапов:
1.Создание сокета. Выбор сетевого домена и типа сокета.
2.Задание параметров сокета (необязательно). Поведение сокета регулируется множеством параметров. Пока сокет открыт, программа может менять любой из них.
3.Привязка к определенному адресу/порту (необязательно). Задание конкретного IP-адреса, а также выбор порта. Если пропустить этот этап, операционная система разрешит связь с любым IP-адресом и назначит произвольный номер порта.
4.Подключение к одноранговому компьютеру/серверу. Организация двунаправленного канала связи между клиентской и другой сетевой программой. Если пропустить этот этап, будет создан канал адресной передачи сообщений без установления соединения.
5.Частичный разрыв соединения (необязательно). Выбор одного из двух режимов работы: прием или передача. Этот этап можно выполнить, если создан запасной канал связи.
6.Прием/передача сообщений (необязательно). Этот этап можно пропустить, если требуется всего лишь проверить, доступен ли сервер.
7.Разрыв соединения. Естественно, этот этап важен: долго выполняющиеся программы могут со временем исчерпать лимит дескрипторов файлов, если не закрывать неиспользуемые сеансы.
Со стороны сервера последовательность действий несколько иная (Рисунок 3).
socket( )
bind( )

“слушающий сокет”
listen( )

accept( )

“ клиентские
сокеты”


close( )



close( )

Рисунок 3 – Последовательность действий сервера при организации взаимодействия
Опишем каждый из этапов: 1. создание серверного (слушающего) сокета при помощи функции socket(). Серверный сокет служит для организации взаимодействий с клиентами. Передача данных по данному сокету невозможна(см. рисунок 4); 2. связь сокет-адреса (адрес Internet и номер порта) с сервисной программой: "binding"; 3. перевод сокета в состояние "прослушивания" входящих соединений;
4.как только какой-либо из клиентов попытается подключиться к серверу, происходит вызов функции accept(), которая создает клиентский сокет(с характеристиками базового слушающего сокета), по которому происходит передача/ прием данных(рисунок 4); 5.прием/передача сообщений (необязательно). Этот этап можно пропустить, если требуется всего лишь проверить, доступен ли сервер.
6.Разрыв соединения.
Рисунок 4 – Взаимодействие клиента и сервера при помощи механизма поточных серверов
На рисунке 5 показано совместное использование клиента и сервера.
Рисунок 5 – Схема взаимодействия клиента и сервера по поточному сокету
Ниже некоторые из этапов описываются подробнее: приводятся примеры и рассматриваются соответствующие системные вызовы
-
Создание сокета socket()
Чтобы создать сокет, программа вызывает функцию socket. Она, в свою очередь, возвращает дескриптор сокета, подобный дескриптору файла. Другими словами, дескриптор сокета указывает на таблицу, содержащую описание свойств и структуры сокета. Следующий пример показывает возможную форму вызова функции socket:
socket_handle = socket(protocol_family, socket_type, protocol);
При создании сокета указываются три параметра: группу, к которой принадлежит протокол, тип сокета и сам протокол.
Первый параметр задает группу или семейство, к которому принадлежит протокол, например семейство TCP/IP. Для указания группы протоколов в интерфейсе сокетов определены символьные константы. Символьная константа PF_INET, например, определяет семейство протоколов TCP/IP. Семейства адресов тесно связаны с семействами протоколов. Форматы адресов различных сетей не одинаковы. Разработчики сокетов в полном соответствии с этим соображением еще больше обобщили интерфейс сокетов, реализовав для работы с различными сетями возможность обращения к различным семействам сетевых адресов. Символьные константы, указывающие на семейство адресов, начинаются с префикса «AF_». Константа, обозначающая семейство адресов Интернет (TCP/IP), называется AF_INET. ввиду тесной взаимосвязи между семействами протоколов и семействами адресов, существует ошибочное мнение, что это одно и то же. К примеру, в интерфейсе сокетов TCP/IP значения констант семейства протоколов (PF_INET) и семейства адресов (AF_INET) равны. Поэтому в настоящее время все равно, какую константу применять. Возможные значения первого параметра:
-
PF_INET для сетевого протокола IPv4;
-
PF_INET6 для IPv6;
-
PF_UNIX для локальных сокетов (используя файл – используется как один из механизмов межпроцессного взаимодействия).
Второй параметр, тип сокета, задает режим соединения: датаграммный или ориентированный на поток байтов. Более подробно:
-
SOCK_STREAM (надёжная потокоориентированная служба (сервис) или потоковый сокет)
-
SOCK_DGRAM (служба датаграмм или датаграмный сокет)
-
SOCK_SEQPACKET (надёжная служба последовательных пакетов)
-
SOCK_RAW (Сырой сокет— «сырой» протокол поверх сетевого уровня).
Параметр «протокол» определяет протокол, с которым будет работать сокет. В сетях TCP/IP все константы начинаются с префикса IPPROTO_. Например, протокол TCP обозначается константой IPPROTO_TCP. Символьная константа IPPROTO_UDP обозначает протокол UDP. Полный перечень поддерживаемых протоколов указан в заголовочном файле <netinet/in.h>.
Следующий оператор демонстрирует, как может выглядеть вызов функции socket:
socket_handle = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
Данный вызов сообщает интерфейсу сокетов о том, что программа желает использовать семейство протоколов Интернет (PF_INET), протокол TCP (IPPROTO_TCP) для соединения, ориентированного на поток байтов (SOCK_STREAM).
На самом деле «создание сокета»— это просто процесс отведения памяти системой для размещения в ней структуры данных, описывающей данный сокет. Дескриптор является указателем на внутреннюю структуру данных.