Это архив сайта coldflame.by.ru, он не обновлялся с 2007 года. Мой современный сайт тут: http://leonid.shevtsov.me.
Домой! Обо мне Специально для РИ-06-1 Разнообразное... барахло, короче :) Программы и прочее Статьи и переводы Блог SmartDaemon
Предыдущая ОглавлениеСледующая

3. Структуры и обработка данных

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

Для начала самое простое: дескриптор сокета. Дескрипторы имеют следующий тип:

int

Самый обычный int.

С этого момента дела пойдут не так хорошо, так что просто читай и разбирайся вслед за мной. Знай: есть два порядка следования байтов: начиная с самого значащего байта (most significant byte, MSB), и с наименее значащего байта (least significant byte, LSB). Первый называется "сетевым порядком байтов". Некоторые машины и так используют сетевой порядок, а некоторые нет. Когда я говорю, что число должно быть в сетевом порядке байтов, ты должен вызвать функцию (вроде htons()) чтобы преобразовать его. Если я не говорю ничего о сетевом порядке, можешь оставить число как оно есть.

(Для любопытных: сетевой порядок байт еще называют "Big-Endian".)

Моя Первая СтруктураTM - struct sockaddr. Эта структура хранит информацию об адресе для многих типов сокетов:

struct sockaddr {
    unsigned short    sa_family;    // семья адресов, AF_xxx
    char              sa_data[14];  // 14 байтов адреса
}; 

sa_family может принимать разные значения, но в этом документе она всегда будет равна AF_INET. sa_data содержит адрес получателя и порт сокета. Упаковывать адрес в эти 14 байт вручную довольно неудобно.

Поэтому программисты придумали параллельную структуру struct sockaddr_in ("in" - "Internet".)

struct sockaddr_in {
    short int          sin_family;  // семья адресов
    unsigned short int sin_port;    // номер порта
    struct in_addr     sin_addr;    // IP-адрес
    unsigned char      sin_zero[8]; // пусто
}; 

Эта структура упрощает работу с адресом. Учти, что sin_zero (который нужен, чтобы размеры структур совпадали) должен быть обнулен с помощью memset(). Важно, что указатель на struct sockaddr_in может быть обкастован в указатель на struct sockaddr и наоборот. Так что несмотря на то, что connect() требует struct sockaddr*, можно все равно использовать struct sockaddr_in и кастовать его в последнюю минуту. Обрати внимание, что sin_family соответствует sa_family в struct sockaddr и должна быть равна "AF_INET". Наконец, sin_port и sin_addr должны быть в сетевом порядке байт!

"Но," спросишь ты, "как вся структура struct in_addr sin_addr, может быть в сетевом порядке байт?" Этот вопрос требует внимательного рассмотрения структуры struct in_addr, одного из худших объединений в мире:

// Интернет-адрес (структура по историческим соображениям)
struct in_addr {
    unsigned long s_addr; // 32 бита, или 4 байта
}; 

То есть, она когда-то была объединением, но сейчас это в прошлом. К счастью. Так что если ты объявил ina типа struct sockaddr_in, то ina.sin_addr.s_addr содержит 4-байтовый IP-адрес (в сетевом порядке байт). Учти, что даже если твоя ОС все еще использует богомерзкое объединение struct in_addr, ты все равно можешь обратиться к адресу тем же образом (благодаря #define'ам.)


3.1. Преобразовываем форматы!

Вот мы и прибыли в следующий раздел. До этого момента я не рассказывал о преобразовании из сетевого в системный порядок - пришла пора это наверстать!

Итак. Есть два типа, которые нужно преобразовывать: short (два байта) и long (четыре ). Эти функции работают также и для вариаций с unsigned. Допустим, что тебе нужно преобразовать значение типа short из системного (Host Byte Order) в сетевой (Network Byte Order) порядок. Название функции собирается по буквам: "Host TO Network Short" -> h-to-n-s -> htons().

Вот так все просто...

Ты можешь использовать любые комбинации "n", "h", "s", and "l", кроме откровенно идиотских. Например, НЕТ такой функции, как stolh() ("Short to Long Host"). Зато есть другие:

  • htons() -- "Host to Network Short" - Системный в Сетевой, short

  • htonl() -- "Host to Network Long" - Системный в Сетевой, long

  • ntohs() -- "Network to Host Short" - Сетевой в Системный, short

  • ntohl() -- "Network to Host Long" - Сетевой в Системный, long

Пока ты обдумываешь этот материал, ты можешь подумать "А как мне поменять порядок байт в char'е?", а потом "гм, неважно". Еще ты можешь решить, что если твой процессор 68000 и так использует сетевой порядок, то можно не вызывать htonl(). В принципе, ты прав, НО если ты решишь портировать код на машину с обратныи порядком, он не будет работать. Будь портабельным! Миром правит Unix! (несмотря на все старания Билла Гейтса). Запомни: всегда размещай байты в Сетевом Порядке перед отпралвением их в Сеть.

Заключение: а почему sin_addr и sin_port должны быть в сетевом порядке, а sin_family нет? Ответ: sin_addr и sin_port инкапсулируются на уровнях IP и UDP, соответственно. Поэтому они должны быть в сетевом порядке. А поле sin_family используется системой для определения типа адреса, и не пересылается по сети. Поэтому оно может быть в системном порядке.


3.2. IP-адреса и как с ними работать

К твоему счастью, есть несколько функций для работы с IP-адресами. Так что их не нужно вычислять вручную и запихивать в long оператором <<.

Допустим, у тебя есть struct sockaddr_in ina, и адрес "10.12.110.57", который ты хочешь записать в нее. Для этого используется функция inet_addr(), которая преобразовывает IP-адрес из стандартной записи в число. Это делается таким образом:

ina.sin_addr.s_addr = inet_addr("10.12.110.57"); 

Обрати внимание, что inet_addr() возвращает адрес в Сетевом Порядке, так что не надо вызывать htonl(). Круто!

Этот кусок кода не очень надежен, так как нет проверки ошибок. Понимаешь, inet_addr() возвращает -1 при ошибке. Помнишь двоичные числа? (unsigned)-1 как раз соответствует IP-адресу 255.255.255.255! Это же широковещательный адрес! Всегда выполняй проверку на ошибки.

На самом деле, есть более удобный интерфейс вместо inet_addr(): он называется inet_aton() ("aton" значит "ascii to network", "текст в сетевой"):

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp); 

А вот пример записи адреса в struct sockaddr_in (этот пример станет понятнее в разделах bind() и connect().)

struct sockaddr_in my_addr;

my_addr.sin_family = AF_INET;         // системный порядок байт
my_addr.sin_port = htons(MYPORT);     // сетевой порядок
inet_aton("10.12.110.57", &(my_addr.sin_addr));
memset(&(my_addr.sin_zero), '\0', 8); // обнуляем остаток структуры

inet_aton(), в отличие от практически любой другой сокетной функции, возвращает не ноль при успехе и ноль при ошибке. А адрес возвращается в inp.

Увы, не все платформы поддерживают inet_aton(), так что в этом руководстве применяется более старая и распространенная inet_addr().

Итак, теперь ты можешь преобразовывать IP-адреса в двоичную запись. А как наоборот? Если есть структура struct in_addr и ты хочешь напечатать адрес в записи с точками? Тогда тебе нужно использовать функцию inet_ntoa() ("ntoa" значит "network to ascii"):

printf("%s", inet_ntoa(ina.sin_addr)); 

Этот код напечатает IP-адрес. Учти, что inet_ntoa() берет аргументом struct in_addr, а не long. Обрати внимание, что она возвращает указатель на строку. Он указывает на статический буфер внутри inet_ntoa(), так что каждый вызов inet_ntoa() будет перезаписывать адрес:

char *a1, *a2;

a1 = inet_ntoa(ina1.sin_addr);  // 192.168.4.14
a2 = inet_ntoa(ina2.sin_addr);  // 10.12.110.57
printf("адрес 1: %s\n",a1);
printf("адрес 2: %s\n",a2); 

напечатает:

адрес 1: 10.12.110.57
адрес 2: 10.12.110.57 

Если ты хочешь сохранить адрес, скопируй его через strcpy() в свой буфер.

Вот и все. Позже ты научишься преобразовывать строки вроде "whitehouse.gov" в соответствующие IP-адреса (см. DNS).

3.2.1. Частные (или отсоединенные) сети

Многие сети спрятаны от остального мира за брандмауэром для защиты. Часто брандмауэр преобразовывает "внутренние" IP-адреса во "внешние" (доступные миру) используя процесс, который называется Network Address Translation (Перевод сетевых адресов), или NAT.

Ты еще не нервничаешь? "На что он намекает?"

Расслабься. Как начинающему, тебе вообще не нужно беспокоиться о NAT, так как он выполняется прозрачно. Но я упомянул об этом на тот случай, если ты получаешь странные адреса за брандмауэром.

Например, у меня дома стоит брандмауэр. Компания-DSL выделила мне два статических IP-адреса, но в сети находится семь компьютеров. Как такое возможно? Два компьютера не могут иметь одинаковый IP, или данные не будут знать, куда идти!

Ответ: они не имеют одинаковый IP. Они находятся в частной сети с 24 миллионами IP-адресов. Вот что происходит:

Когда я соединяюсь с удаленным компьютером, он сообщает, что я зашел с 64.81.52.10 (не настоящий IP). Но мой компьютер сообщает, что мой IP 10.0.0.5. Кто же переводит IP-адреса? Брандмауэр! Он выполняет NAT!

10.x.x.x - один из зарезервированных номеров, которые могут использоваться только в полностью отсоединенных сетях или сетях за брандмауэрами. Детально доступные сетям адреса рассматриваются в RFC 1918, но ты можешь часто видеть 10.x.x.x и 192.168.x.x, где x=0-255, . Реже - 172.y.x.x, где y =16-31.

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


Предыдущая ОглавлениеСледующая