Transcript Со́кеты - "Web-программирование", "Системное программное
СЕТЕВОЕ ПРОГРАММИРОВАНИЕ В LINUX
Со́кеты
Со́кеты
— (англ. socket — углубление, гнездо, разъём) название программного интерфейса для обеспечения обмена данными между процессами.
Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.
Существуют (блокируемые) и асинхронные (неблокируемые).
Синхронные сокеты задерживают управление на время два выполнения вида об этом вызывающий код.
сокетов операции, а —синхронные асинхронные возвращают его немедленно, продолжая выполнение в фоновом режиме, и, закончив работу, уведомляют
Со́кеты
Сокеты, независимо от вида, делятся на два типа: Потоковые; Дейтаграммные.
Потоковые обоих сокеты сторон и работают гарантируют с установкой соединения, обеспечивая надежную идентификацию целостность и успешность доставки данных.
Дейтаграмные сокеты работают без установки соединения и не обеспечивают ни идентификации отправителя, ни контроля успешности доставки данных, зато они заметно быстрее потоковых.
Выбор типа сокетов определяется транспортным протоколом на котором работает сервер, - клиент не может по своему желанию установить с дейтаграммным сервером потоковое соединение.
Со́кеты
Замечание: дейтаграммные сокеты опираются на протокол UDP, а потоковые на TCP.
Кроме UDP непосредственно приложению. При использовании таких сокетов пакет не проходит через фильтр TCP/IP, предстает в своей серой форме. В таком случае обязанность обработать все данные и выполнить такие действия, как удаление заголовков и разбор полей, ложиться на получающее приложение – все равно что включить в приложение небольшой стек TCP/IP.
в потоковых стеке и TCP/IP дейтаграмных сокетов существуют сырые сокеты. Серый сокет – это сокет, который принимает пакеты, обходит уровни TCP и и отправляет их то есть никак не обрабатывается, и
Со́кеты
Серые сокеты главным образом используются при разработке ping.
специализированных рамками данного курса.
низкоуровневых протокольных приложений, таких как traceroute и Работа с серыми сокетами требует солидного знания базовых протоколов TCP/UDP/IP и лежит за В Unix дескрипторы сокетов реализованы так же, как дескрипторы фалов.
дескрипторами сокетов.
В действительности большинство функций, работающих с дескрипторами файлов, таких как read или write, будут работать и с
Со́кеты
Перед использованием необходимости сокетов проводить в программе необходимо подключить библиотеки. В Unix нет инициализацию и деинициализацию библиотеки для семейства Unix необходимы библиотеки : работы с сокетами, как в Windows. Библиотеку достаточно просто подключить. Для написания сетевого клиент серверного приложения для операционных систем sys/types.h содержит определения системных типов данных, которые используются в следующих двух заголовочных файлах; sys/socket.h
содержит определения необходимых для работы с сокетами; структур, netinet/in.h содержит константы и структуры, необходимые для пространства адресов интернета.
Со́кеты
В программе сокет идентифицируется дескриптором это просто переменная типа int. Программа получает дескриптор от операционной системы при создании сокета, а затем передаёт его сервисам
socket API
для указания сокета, над которым необходимо выполнить то или иное действие.
int socket(int af, int type, int protocol);
Функция создания сокета принципиально ничем не отличается windows).
от ее аналога в Windows (см.
методические указания к л.р. по сокетам для Стоит упомянуть, что первый слева параметр AF_UNIX, в Unix может принимать соответствующее сокетам пространстве имен.
значение в файловом
Адреса
Прежде чем передавать данные через сокет, его необходимо связать с адресом в выбранном домене.
Вид адреса зависит от выбранного вами домена. В Unix домене это текстовая строка - имя файла, через который происходит обмен данными. В Internet домене адрес задаётся комбинацией IP-адреса и 16 битного номера порта. IP-адрес определяет хост в сети, а порт - конкретный сокет на этом хосте.
Протоколы TCP и UDP используют различные пространства портов.
Для явного связывания сокета с некоторым адресом используется функция bind. Её прототип имеет вид: #include
Адреса
В качестве этой структуры.
первого параметра передаётся дескриптор сокета, который мы хотим привязать к заданному адресу. Второй параметр, addr, содержит указатель на структуру с адресом, а третий - длину struct sockaddr { unsigned short sa_family;// Семейство адресов, AF_xx char sa_data[14]; // 14 байтов для хранения адреса }; Поле sa_family содержит идентификатор домена, тот же, что и первый параметр функции socket. В зависимости от значения этого поля по-разному интерпретируется содержимое массива sa_data.
Адреса
Можно использовать вместо sockaddr одну из альтернативных структур вида sockaddr_XX (XX суффикс, обозначающий домен: "un" - Unix, "in" Internet и т. д.). При передаче в функцию bind указатель на эту структуру приводится к указателю на sockaddr.
Существует два порядка хранения байтов в слове и двойном слове. Один из них называется порядком хоста (
host byte order
), другой - сетевым порядком (
network byte order
) IP адреса и хранения байтов. При указании номера порта необходимо преобразовать число из порядка хоста в сетевой.
Для этого используются функции htons (
Host TO Network Short
Обратное преобразование выполняют функции ntohs и ntohl.
) и htonl (Host TO Network Long).
Установка соединения (сервер)
Установка соединения на стороне сервера состоит из четырёх этапов, ни один из которых не может быть опущен. Сначала сокет создаётся и привязывается к локальному адресу.
константу INADDR_ANY.
Если компьютер имеет несколько сетевых интерфейсов с различными IP адресами, вы можете принимать соединения только с одного из них, передав его адрес функции bind.
Если же вы готовы соединяться с клиентами через любой интерфейс, задайте в качестве адреса
serv_addr.sin_addr.s_addr = INADDR_ANY;
Что касается номера порта, вы можете задать конкретный номер или 0 (в этом случае система сама выберет произвольный неиспользуемый в данный момент номер порта).
Установка соединения (сервер)
Далее создаётся очередь запросов на соединение.
Сокет переводится в режим ожидания запросов со стороны клиентов. Всё это выполняет функция listen: // ожидание подключений, размер очереди - 5
listen(sockfd,5);
Первый параметр - дескриптор сокета, а второй задаёт размер очереди запросов. Каждый раз, когда очередной клиент пытается соединиться с сервером, его запрос ставится в очередь, так как сервер может быть занят обработкой других запросов. Если очередь заполнена, все последующие запросы будут игнорироваться. Когда сервер готов обслужить очередной запрос, он использует функцию accept.
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
Установка соединения (сервер)
Функция accept создаёт для общения с клиентом новый сокет и возвращает его дескриптор в переменную newsockfd типа int. Параметр sockfd задаёт слушающий сокет. После вызова он остаётся в слушающем состоянии и может принимать другие соединения. В структуру, на которую ссылается cli_addr, записывается переменную, адрес адресуемую сокета указателем клиента, который установил соединение с сервером. В clilen, изначально записывается размер функция accept записывает туда длину, которая реально была использована. Если вас не интересует адрес клиента, вы можете просто передать NULL в качестве второго и третьего параметров.
структуры;
Установка соединения (клиент)
На стороне клиента для установления соединения используется функция connect.
connect(sockfd,(struct sockaddr &serv_addr, sizeof(serv_addr)) *)
Здесь sockfd сокет, длину этой структуры.
который будет использоваться для обмена данными с сервером, serv_addr содержит указатель на структуру с адресом сервера, а addrlen -
Обмен данными
Для обмена данными используются функции send и recv.
В Unix для работы с сокетами можно использовать также файловые функции read и write, но они обладают меньшими возможностями. Функция send используется для отправки данных и имеет следующий прототип.
int send(int sockfd, const void *msg, int len, int flags);
Здесь sockfd - это дескриптор сокета, через который мы отправляем данные, msg - указатель на буфер с данными, len - длина буфера в байтах, а flags - набор битовых флагов, управляющих работой функции (если флаги не используются, передайте функции 0).
Обмен данными
Флаг MSG_OOB используется для приёма срочных данных, а MSG_DONTROUTE запрещает маршрутизацию MSG_DONTROUTE.
пакетов.
Нижележащие транспортные слои могут проигнорировать флаг Функция send возвращает число байтов, которое на самом деле было отправлено (или -1 в случае ошибки).
Для чтения данных из сокета используется функция recv.
int recv(int sockfd, void *buf, int len, int flags);
Обмен данными
В целом её использование аналогично send.
Она точно так же принимает дескриптор сокета, указатель на буфер и набор флагов.
Флаг MSG_OOB используется для приёма срочных данных, а MSG_PEEK заставляет функцию recv просматривать данные вместо их чтения.
Закрытие сокета
Закончив обмен данными, закройте сокет с помощью функции close. Это приведёт к разрыву соединения.
#include
int close(int fd);
Можно также запретить передачу данных в каком-то одном направлении, используя shutdown.
int shutdown(int sockfd, int how); Параметр how может принимать одно из следующих значений: 0 запретить чтение из сокета 1 2 запретить запись в сокет запретить и то и другое
Закрытие сокета
Хотя после вызова shutdown с параметром how, равным 2, больше нельзя использовать сокет для обмена данными, но всё равно потребуется вызвать close, чтобы освободить связанные с ним системные ресурсы.
Если что-то пошло не так, все рассмотренные функции возвращают -1, записывая в глобальную переменную предпринять выполнения.
errno действия А код можно по ошибки.
Можно проанализировать значение этой переменной и восстановлению нормальной работы программы, не прерывая её просто выдать диагностическое сообщение и завершить программу: perror(msg); //msg – сообщение об ошибке exit(1);
Примеры
Примеры клиентских и серверных приложений, использующих как протокол TCP, так и UDP, доступны на сайте дисциплины:
http://gun.cs.nstu.ru/ssw/Linsockets/
Функции для работы с DNS
Поскольку для идентификации хостов в Internet широко используются доменные имена, необходимо изучить механизм их преобразования в IP-адреса.
Кроме того имеются несколько удобных вспомогательных функций.
IP адреса принято записывать в виде четырёх чисел, разделённых точками. Для преобразования адреса, записанного в таком формате, в число и наоборот используется семейство функций
inet_addr
,
inet_aton
и
inet_ntoa
.
Функции для работы с DNS
#include
Функции для работы с DNS
Функция
inet_addr
принимает строку и возвращает адрес (уже с сетевым порядком следования байтов).
Проблема с этой функцией состоит в том, что значение -1, возвращаемое ею в случае ошибки, является в то же время корректным адресом 255.255.255.255
почему сейчас рекомендуется использовать более новую функцию (широковещательный адрес). Вот
inet_aton
(Ascii TO Network).
Для обратного преобразования используется функция
inet_ntoa
(Network TO Ascii).
Обе эти функции работают с адресами в сетевом формате. В случае ошибки они возвращают 0, а не -1.
Функции для работы с DNS
Для преобразования доменного имени в IP адрес используется функция
gethostbyname
.
#include
получает имя хоста и возвращает указатель на структуру с его описанием. Рассмотрим эту структуру более
Функции для работы с DNS
struct hostent { char *h_name; char **h_aliases; int int h_addrtype; h_length; char **h_addr_list; }; #define h_addr h_addr_list[0]
Функции для работы с DNS
h_name
.
Имя хоста.
h_aliases
.
Массив строк, содержащих псевдонимы хоста. Завершается значением NULL.
h_addrtype AF_INET
.
.
Тип адреса. Для Internet-домена -
h_length
.
Длина адреса в байтах.
h_addr_list
.
Массив, содержащий адреса всех сетевых интерфейсов хоста. Завершается нулём.
Обратите внимание, что байты каждого адреса хранятся с сетевым порядке, поэтому
htonl
вызывать не нужно.
Функции для работы с DNS
Как видим,
gethostbyname
возвращает достаточно полную информацию. Если нас интересует адрес хоста, мы можем выбрать его из массива макрос
h_addr
).
h_addr_list
.
Часто берут самый первый адрес (как указано выше, для ссылки на него определён специальный Для определения имени хоста по адресу используется функция
gethostbyaddr
.
Вместо строки она получает адрес (в виде
sockaddr
) и возвращает указатель на ту же самую структуру
hostent
.
Функции для работы с DNS
Используя эти две функции, нужно помнить, что они сообщают об ошибке не так, как остальные: вместо указателя возвращается
NULL
, а расширенный код ошибки записывается в глобальную переменную
h_errno
(а не использовать
errno herror
).
Соответственно, для вывода диагностического сообщения следует вместо
perror
.
Функции для работы с DNS
Следует иметь в виду, предыдущем обращении.
что функции
gethostbyname
одной из и этих
gethostbyaddr
возвращают указатель на статическую область памяти.
Это означает, что каждое новое обращение к функций приведёт к перезаписи данных, полученных при
Функции для работы с DNS
В заключение рассмотрим семейство полезных функций -
getsockname
и
getpeername
.
ещё одно
gethostname
, #include
gethostname gethostbyname
.
используется для получения имени локального хоста. Далее его можно преобразовать в адрес при
Функции для работы с DNS
#include < sys/socket.h
> int getsockname(int socklen_t *
namelen
);
s
, struct sockaddr *
name
,
getsockname
возвращает текущее указанного сокета в параметре
name
.
имя В параметре
namelen
должно быть указано, сколько места выделено под
name
.
При возврате в этом параметре передается реальный использованный размер в байтах.
Функции для работы с DNS
#include
getpeername
момент узнать адрес сокета на "другом конце" соединения. Она получает дескриптор сокета, соединённого с удалённым хостом, и записывает адрес этого хоста в структуру, на которую указывает
addr
.
позволяет в любой
Функции для работы с DNS
Фактическое количество записанных байт помещается по адресу
addrlen
(не забудьте записать туда размер структуры
addr
до вызова
getpeername
необходимости ).
Полученный адрес при можно преобразовать в строку, используя
inet_ntoa
или
gethostbyaddr
.
назначению позволяет Функция обратна определить "нашем конце" соединения.
getsockname getpeername
адрес сокета по и на
Использование сырых сокетов
Сырые (низкоуровневые, беспротокольные) сокеты предоставляют программисту полный контроль над содержимым пакетов, которые отправляются по сети. Но они сложнее в использовании их следует и только обладают в случае плохой переносимостью. Вот почему использовать явной необходимости.
Например, типа ping и traceroute.
без них не обойтись при разработке системных утилит
Использование сырых сокетов
Первым делом выясним, чем сырые сокеты отличаются от обычных. Работая с обычными сокетами, вы передаёте системе "чистые" данные, а она сама заботится о добавлении к ним необходимых заголовков. Например, когда вы посылаете сообщение через UDP сокет, к нему добавляется сначала UDP заголовок, потом IP-заголовок, а в самом конце - заголовок аппаратного протокола, который используется в локальной сети (обычно Ethernet). В результате получается кадр, показанный на рисунке.
Использование сырых сокетов
Использование сырых сокетов
Сырые сокеты позволяют включать в буфер с данными сообщение заголовки TCP нужных или протоколов.
Например, вы можете включить в ваше UDP заголовок, предоставив заголовков.
системе сформировать IP заголовок, а можете вообще сформировать все заголовки самостоятельно. При этом придётся изучить работу соответствующих протоколов и строго соблюсти формат их
Использование сырых сокетов
При работе с сырыми сокетами необходимо указывать в третьем параметре функции
socket
файле тот протокол, к заголовкам которого вы хотите получить доступ. Константы для основных протоколов Internet объявлены в
netinet/in.h
.
Они имеют вид
IPPROTO_XXX
, где XXX-название протокола:
IPPROTO_TCP
,
IPPROTO_UDP
,
IPPROTO_RAW
(последний дает возможность поработать с "сырым" IP и формировать IP-заголовки вручную).
Использование сырых сокетов
Низкоуровневые (беспротокольные, сырые) сокеты могут использоваться только процессами, пользователя с эффективным UID=0 (root).
Это вызвано тем, что стек TCPIP полученный сырым сокетом пакет на сырые сокеты того уровня, который указан в поле Protocol IP заголовка пришедшего пакета.
запущенными от имени
скопирует все
Использование сырых сокетов
ВСЕ -
это даже те, которые открыты в других приложениях (именно суперпользователя).
по этой причине приложение, оперирующее сырыми сокетами, может быть запущено только с правами Впрочем, суперпользователь командой setcap CAP_NET_RAW=ep .
/приложение разрешает указанному приложению работать с сырыми сокетами от имени обычных пользователей.
Использование сырых сокетов
Отметим еще одну важную особенность. Не зависимо от уровня сырого сокета, ему доставляется полный пакет, включающий IP заголовки. Таким образом, каждый сырой сокет в Linux является сниффером на том уровне, на котором он был создан. Следует помнить об этом при разработке приложений.
Использование сырых сокетов
Все числовые данные в заголовках должны записываться в сетевом формате. Поэтому не забывайте использовать функции
htons
и
htonl
.
Для примера рассмотрим программу-клиент с использованием сырых UDP-сокетов. При этом необходимо вручную формировать UDP заголовок отправляемого сообщения.
Протокол UDP выбран потому, что у него заголовок выглядит совсем просто.
Использование сырых сокетов
Использование сырых сокетов
Код примера sender.c представлен на сайте в папке Linsockets/Raw.
Программа-сервер является обычным UDP сервером.
Код сервера receiver.c
представлен на сайте в папке Linsockets/Raw.
Необходимо обратить внимание на несколько моментов. Во-первых, можно не задавать номер порта в структуре
sockaddr_in
.
Использование сырых сокетов
Поскольку этот номер содержится в UDP заголовке, от поля
sin_port
уже ничего не зависит. Во-вторых, в примере в качестве контрольной суммы записан ноль, чтобы не утомлять себя её вычислением. Протокол UDP является ненадёжным по своей природе, поэтому он допускает подобную вольность. Но другие протоколы (например, IP) могут и не допускать. В-третьих, все данные UDP-заголовка форматируются с использованием
htons
.
Примеры сырых сокетов
Наиболее типичным примером применения сырых сокетов являются известные утилиты
ping
и
traceroute
.
Необходимо, однако, учитывать, что реализация
ping
требует умения формировать заголовки кадра Ethernet, в частности, указывать MAC-адрес отправителя.
Пример кода ping.c
представлен на сайте в папке Linsockets/Raw.
Для запуска программы суперпользователя или разрешение CAP_NET_RAW.
нужны данное права им
Примеры сырых сокетов
Реализация
traceroute
требует знания протокола ARP для формирования кадра Ethernet с ARP-запросом и разбора такого же кадра с ответом. Пример кода traceroute.c
представлен на сайте в папке Linsockets/Raw.
Запуск этого примера тоже потребует наличия соответствующих привилегий.