Готовый перевод Michael J. Donahoo "TCP/IP Sockets in C, 2nd Edition" / Michael J. Donahoo "Программирование TCP/IP сокетов в Си, 2-ое издание": 2.4 Указание адресов
2.4 Указание адресов
Приложения, использующие сокеты, нуждаются в возможности идентификации удаленных точек, с которыми они будут взаимодействовать.
Из предыдущего следует, что клиент должен указать адрес и номер порта сервера приложений, с которыми он собирается взаимодействовать.
Кроме того, уровень сокетов иногда требует передачи адресов в приложение.
Например, функция, аналогичная "Запрос ID" в телефонной сети позволяет серверу узнать адрес и номер порта для каждого клиента, который общается с ним.
В этом разделе мы рассмотрим структуры данных, используемые в качестве контейнеров для этой информации в Sockets API.
2.4.1 Общие адерса
Sockets API определяет общий тип данных - структуру sockaddr - для определения адресов, связанных с сокетами:
struct sockaddr {
sa_family_t sa_family; // Схема адресации (например, AF_INET)
char sa_data[14]; // Зависимая от схемы адресации адресная информация
};
Первая часть этой адресной структуры определяет схему адресации - адресное пространство, к которому принадлежит адрес.
Для наших целей мы будем использовать системно независимые константы af_inet и af_inet6, которые будут определять семейство интернет адресов IPv4 и IPv6 соответственно.
Вторая часть структуры является последовательностью бит, конкретный вид которых зависит от семейства адресов. (Это типичный способ борьбы с неоднородностью в операционных системах и сетях.)
Как мы обсуждали в пункте 1.2, адрес сокета для семейства протоколов интернета имеет две части: 32-битный (IPv4) или 128-битный (IPv6) интернет адрес и 16-битный номер порта [Внимательный читатель может заметить, что общая структура sockaddr не сможет вместить в себя 16-байтный IPv6 адрес и 2-байтный номер порта. Мы еще столкнемся с этой трудностью в ближайшее время.].
2.4.2 IPv4 адреса
Конкретный вид структуры sockaddr, который используется в для TCP/IP адресов сокетов, зависит от версии IP. Для IPv4 используется структура sockaddr_in.
struct in_addr {
uint32_t s_addr; // Интернет адрес (32 бита)
};
struct sockaddr_in {
sa_family_t sin_family; // Интернет протокол (AF_INET)
in_port_t sin_port; // Порт адреса (16 бит)
struct in_addr sin_addr; // IPv4 адрес (32 бита)
char sin_zero[8]; // Не используется
};
Как вы видите, структура sockaddr_in имеет поля для номера порта и интернет адреса в дополнение к полю адресного семейства.
Важно понимать, что sockaddr_in это более подробное представление данных, содержащихся в структуре sockaddr, с учетом использования сокетов IPv4.
Таким образом, мы можем заполнить поля структуры sockaddr_in и потом поместить ее (или указатель на нее) вместо sockaddr (или указателя уже на эту структуру) в функции сокета, которые по полю sa_family будут определять фактический тип структуры, а затем сможем вернуться к соответствующему типу.
2.4.3 IPv6 адреса
В случае IPv6 используется структура sockaddr_in6.
struct in_addr {
uint32_t s_addr[16]; // Интернет адрес (128 бит)
};
struct sockaddr_in6 {
sa_family_t sin6_family; // Интернет протокол (AF_INET6)
in_port_t sin6_port; // Порт адреса (16 бит)
uint32_t sin6_flowinfo; // Информация потока
struct in6_addr sin6_addr; // Адрес IPv6 (128 бит)
uint32_t sin6_scope_id; // Область идентификатора
};
Структура sockaddr_in6 имеет дополнительные поля помимо тех, которые есть в структуре sockaddr_in.
Они предназначены для реализации возможностей протокола IPv6, которые обычно не используются. Они будут (в основном) игнорироваться в этой книге.
Как и в случае со структурой sockaddr_in, мы должны использовать структуру sockaddr_in6 (указатель на нее) вместо структуры sockaddr (указателя на нее) в различных функциях сокета.
Опять же, программная реализация использует поле адреса семейства адресов для определения реального типа аргумента.
2.4.4 Основы хранения адресов
Если вы что-нибудь знаете о размещении структур данных в С, вы, возможно, уже заметили что структура sockaddr не является достаточно большой, чтобы в ней разместилась структура sockaddr_in6.
(Если вы ничего об этом не знаете, не огорчайтесь: многое из того, что вам нужно знать, будет рассмотрено в главе 5.)
В частности, что, если мы хотим выделить место для структуры адреса, но не знаем фактического типа адреса (т.е. IPv4 или IPv6)? Основная структура sockaddr не будет работать, так как она является слишком маленькой для некоторых типов адресов.
[Вы можете удивиться, почему это так (почему мы так делаем). Причины, очевидно, имеют отношение к обратной совместимости: API сокетов было создано достаточно давно, до возникновения IPv6, когда ресурсы были ограниченными и не было никакой необходимости иметь структуру адреса большого размера. Изменение ее размера сейчас на больший, по-видимому, нарушит двоичную совместимость с некоторыми приложениями.]
Для решения этой проблемы проектировщики сокетов создали структуру sockaddr_storage, которая которая гарантированно будет такого размера, чтобы вместить все поддерживаемые типы адресов.
struct sockaddr_storage {
sa_family_t
...
// Отступы и поля, чтобы получить правильную длину и выравнивание
...
};
Как и в структуре sockaddr, мы имеем здесь первое поле, определяющее фактический тип адреса; однако в структуре sockaddr_storage у нас есть достаточно места для любого типа адреса.
(Для получения представления о том, как это может быть достигнуто, обратитесь к обсуждению того, как компилятор C размещает структуры в памяти, в разделе 5.1.6.)
И последнее замечание по адресам. На некоторых платформах, структуры адреса содержат дополнительное поле, в котором хранится длина структуры адреса в байтах.
Для структур sockaddr, sockaddr_in, sockaddr_in6 и sockaddr_storage дополнительные поля называются sa_len, sin_len, sin6_len и ss_len соотвественно.
Так как поле длины имеется не во всех системах, не используйте его.
Как правило, для платформ с этой формой структуры определяют специальное значение (например, sin6_len), которое может быть проверено во время компиляции, чтобы узнать, существует ли поле длины структуры.
2.4.5 Бинарные/Строковые преобразования адресов
Для функций сокетов адреса должны быть представлены в "числовой" (т.е. бинарной) форме, однако для человека больше подходит стрка с "версией для печати" (например, 192.168.1.1 или 1::1).
Мы можем преобразовать адрес из печатной строки к числовому виду при помощи функции inet_pton() (pton = printable to numeric - печатное к числовому):
int inet_pton(int addressFamily, const char *src, void *dst)
Первый параметр, addressFamily, определяет семейство адресов, в которое нужно конвертировать.
Напомним, что Sockets API предоставляет общий интерфейс для большого числа областей связи.
Тем не менее здесь нас интересуют только IPv4 (af_inet) и IPv6 (af_inet6).
Параметр src является ссылкой на заканчивающуюся символом '\0' строку, содержащую адрес, который нужно преобразовать.
Параметр dst указывает на блок памяти в пространстве вызова, зарезервированный для результата: его величина должна быть достаточной для хранения результата (по крайней мере 4 байта для IPv4 и 16 байт для IPv6).
inet_pton() возвращает 1, если преобразование произошло успешно, при этом dst указывает на адрес в сетевом байтовом представлении; 0, если строка, на которую указывает src не отформатирована должным образом; и -1, если указанное семейство адресов неизвестно.
Мы можем пойти другим путем для конвертации адреса из числовой в печатную форму, воспользовавшись функцией inet_ntop() (ntop = numeric to printable - от числа к печатному представлению):
const char *inet_ntop(int addressFamily, const void *src, char *dst, socklen_t dstBytes)
Первый параметр, addressFamily, определяет тип адреса, который будет конвертирован.
Второй параметр src является указателем на первый байт блока памяти, содержащего числовой адрес для конвертации.
Размер блока определяется семейством адресов.
Параметр dst указывает на буфер (блок памяти), выделенный в пространстве вызова, куда будет скопирована результирующая строка, ее размер определяется через dstBytes.
Как мы узнаем, какой размер должен иметь блок памяти? Системно определенные константы inet_addrstrlen (для IPv4) и inet6_addrstrlen (для IPv6) показывают возможную длину строки результата (в байтах).
inet_ntop() возвращает указатель на строку, содержащую печатаемый адрес (т.е. третий аргумент), если преобразование произошло успешно, или NULL в противном случае.
2.4.6 Получение адресов, связанных с сокетом
Система связывает локальный и сторонний адрес для каждого подключенного сокета (TCP или UDP).
Позже мы детально обсудим, как эти значения соотносятся.
Мы можем определить эти адреса при помощи функции getsockname() для локального адреса и getpeername() для стороннего адреса.
Оба метода возвращают структуру sockaddr, содержащую информацию об интернет адресе и порте.
int getpeername(int socket, struct sockaddr *remoteAddress, socklen_t *addressLength)
int getsockname(int socket, struct sockaddr *localAddress, socklen_t *addressLength)
Параметр socket является дескриптором сокета, адресную информацию которого мы хотим получить.
Параметры remoteAddress и localAddress указывают на адресные структуры, куда будет помещена адресная информация в результате выполнения методов; они всегда будут приведены к sockaddr * в результате вызова.
Если мы заранее не знаем, какая версия IP протокола используется, мы должны послать внутрь метода sockaddr_storage для получения результата.
Как и в других вызовах сокетов, использующих sockaddr, параметр addressLength является параметром ввода/вывода, определяющим величину буфера в байтах, куда будет возвращена адресная структура.
Внимание! Этот перевод, возможно, ещё не готов.
Его статус: идёт перевод
Переведено на Нотабеноиде
http://notabenoid.org/book/29398/100603
Переводчики: victor8632, prefer211
Настройки
Готово:
