Со́кеты - "Web-программирование", "Системное программное

Download Report

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 #include int bind(int sockfd, struct sockaddr *addr, int addrlen);

Адреса

      В качестве этой структуры.

первого параметра передаётся дескриптор сокета, который мы хотим привязать к заданному адресу. Второй параметр, 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  #include  #include  int inet_aton(const char *cp, struct in_addr *in_p);  unsigned long int inet_addr(const char *cp);  char *inet_ntoa(struct in_addr in);

Функции для работы с DNS

 Функция

inet_addr

принимает строку и возвращает адрес (уже с сетевым порядком следования байтов).

Проблема с этой функцией состоит в том, что значение -1, возвращаемое ею в случае ошибки, является в то же время корректным адресом 255.255.255.255

почему сейчас рекомендуется использовать более новую функцию (широковещательный адрес). Вот

inet_aton

(Ascii TO Network).

Для обратного преобразования используется функция

inet_ntoa

(Network TO Ascii).

Обе эти функции работают с адресами в сетевом формате. В случае ошибки они возвращают 0, а не -1.

Функции для работы с DNS

 Для преобразования доменного имени в IP адрес используется функция

gethostbyname

.

 #include   struct *name); hostent *gethostbyname(const char Эта функция подробно.

получает имя хоста и возвращает указатель на структуру с его описанием. Рассмотрим эту структуру более

Функции для работы с 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   int gethostname(char *hostname, size_t size); Функция помощи

gethostname gethostbyname

.

используется для получения имени локального хоста. Далее его можно преобразовать в адрес при

Функции для работы с DNS

 #include < sys/socket.h

>   int getsockname(int socklen_t *

namelen

);

s

, struct sockaddr *

name

,

getsockname

возвращает текущее указанного сокета в параметре

name

.

имя В параметре

namelen

должно быть указано, сколько места выделено под

name

.

При возврате в этом параметре передается реальный использованный размер в байтах.

Функции для работы с DNS

 #include   int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); Функция

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.

Запуск этого примера тоже потребует наличия соответствующих привилегий.