Guide to Network Сетевое программирование от Биджа
Скачать 1.34 Mb.
|
getaddrinfo(). Она возвращает указатель на новый связанный список этих структур, содержащих всё, что вам надо. Вы можете приказать использовать IPv4 или IPv6 в поле ai_family или оставить AF_UNSPEC чтобы использовать любой. Это круто, поскольку ваш код сможет быть независимым от версии IP. Заметьте, что это связанный список ai_next указывает наследующий элемент - результатов может быть несколько, есть из чего выбирать. Я использую первый рабочий, ау вас могут быть другие нужды. Я же всего не знаю, чувак Как видите поле ai_addr в структуре addrinfo это указатель на структуру sockaddr. Вот мы и полезли во внутренности структур IP адреса. Обычно вам нет нужды заполнять эти структуры, гораздо чаще достаточно вызвать getaddrinfo() и всё нужное там. Однако, выбудете всматриваться во внутренности этих структур, чтобы получить их значения, так что я их здесь представляю. Кроме того, весь код, написанный до изобретения структуры addrinfo, вставлял все эти принадлежности вручную, так что вы обнаружите много дурного кода IPv4, который именно таки поступает. Ивы знаете, ив старых версиях этого руководства ив других) Некоторые структуры относятся к IPv4, некоторые к IPv6, некоторые к обоим. Я отмечу что есть что. В любом случае, структура sockaddr содержит адресную информацию для многих типов сокетов. struct sockaddr { unsigned short sa_family; // семейство адресов, AF_xxx char sa_data[14]; // 14 байт адреса протокола }; sa_family может быть разнообразной, но будет либо AF_INET (IPv4) или AF_INET6 для всего, что мы делаем в этом документе. sa_data содержит адрес назначения и номер порта для сокета. Это весьма громоздко, но ведь вы не хотите утомительно упаковывать адрес в sa_data вручную. Для работы со структурой sockaddr программисты создают параллельную структуру struct sockaddr_in (“in” это “Internet”) для использования с IPv4. И что важно, указатель на структуру sockaddr_in может быть приведен к указателю на структуру sockaddr и наоборот. Так что даже хоть connect() и требует struct 13 Beej's Guide to Network Programming sockaddr* вы всё равно можете пользоваться структурой sockaddr_in и привести её в последний момент // (Только для IPv4 — для IPv6 смотри struct sockaddr_in6) ! struct sockaddr_in { short int sin_family; // Семейство адресов, AF_INET unsigned short int sin_port; // Номер порта struct in_addr sin_addr; // Интернет адрес unsigned char sin_zero[8]; // Размер как у struct sockaddr }; Эта структура позволяет обращаться элементам адреса сокета. Обратите внимание, что sin_zero , который включён для расширения длины структуры до длины sockaddr, должен быть обнулён функцией memset(). Также заметьте, что sin_family соответствует sa_family структуры sockaddr и должен быть установлен в “AF_INET”. Напоследок, должен быть в Порядке Байтов Сети (используйте htons()!) Копнём глубже Поле sin_addr это структура in_addr. Что это значит Не будем излишне драматичны, но это одна из самых пугающих структур union всех времён: // (Только для IPv4 — для IPv6 смотри struct sockaddr_in6_addr) ! // Интернет адрес (исторически обоснованная структура) struct in_addr { uint32_t s_addr; // это 32-битный int (4 байта) }; Тпру Ей положено быть union, но, похоже, эти дни прошли. Скатертью дорожка. Так что, если вы объявили ina типа struct sockaddr_in, то ina.sin_addr.s_addr ссылается на 4-байтный IP адрес (в Порядке Байтов Сети. Отметим, что даже если ваша система до сих пор использует богопротивный union для структуры in_addr, вы всё равно можете ссылаться на 4-байтный IP адрес так как я сделал это выше (это благодаря #define-ам). Как насчёт IPv6? Подобные структуры существуют и для него // (Только для IPv6 —- для IPv4 смотри struct sockaddr_in и struct in_addr) ! struct sockaddr_in6 { u_int16_t sin6_family; // семейство адресов, AF_INET6 u_int16_t sin6_port; // номер порта, Порядок Байтов Сети u_int32_t sin6_flowinfo; // потоковая информация IPv6 struct in6_addr sin6_addr; // адрес IPv6 u_int32_t sin6_scope_id; // Scope ID }; struct in6_addr { unsigned char s6_addr[16]; // адрес IPv6 } Отметим, что IPv6 имеет адрес и номер порта также, как IPv4. Также я пока не собираюсь говорить о полях flow information и Scope ID IPv6… это ведь пособие для начинающих. :-) И последнее, ноне совсем, есть ещё одна простая структура struct sockaddr_storage, созданная достаточно большой, чтобы содержать обе IPv4 и IPv6 структуры. Судя по некоторым вызовам вы не знаете наперёд каким адресом загружать вашу структуру sockaddr: IPv4 или IPv6. Так передайте эту параллельную структуру, подобную struct sockaddr, только больше, и приведите к нужному типу struct sockaddr_storage{ sa_family_t ss_family; // семейство адресов ! 14 Beej's Guide to Network Programming // это выравнивание длины, зависит от реализации, проигнорируйте char __ss_pad1[SS_PAD1SIZE]; int64_t __ss_align; char __ss_pad2[SS_PAD2SIZE]; Важно, что в поле ss_family вы можете посмотреть это AF_INET или AF_INET6, и когда нужно привести её кили адреса, Часть Вторая К счастью для вас существует пачка функций, позволяющих манипулировать IP адресами. Рисовать их вручную и упаковывать в long оператором “<<“ нет нужды. Допустим, у вас есть структура struct sockaddr_in ina и IP адрес “10.12.110.57” или “2001:db8:63b3:1::3490” который вы хотите вне поместить. Вам нужно воспользоваться функцией inet_pton(), которая преобразует IP адрес в записи цифры- точки в структуру struct in_addr либо struct in6_addr в зависимости от указанных AF_INET или AF_INET6. (“pton” означает “presentation to network” или можете называть “printable to network”, если так проще запомнить. Преобразование можно сделать так struct sockaddr_in sa; // IPv4 struct sockaddr_in6 sa6; // IPv6 inet_pton(AF_INET, “192.0.2.1”, &(sa.sin_addr)); // IPv4 inet_pton(AF_INET6, “2001:db8:63b3:1::3490”, &(sa6.sin6_addr)); // IPv6 Быстрое примечание старый способ использования функций inet_addr() и inet_aton() устарели не работает с IPv6.) И ещё, приведённый выше отрезок кода не очень надёжен, потому что здесь нет проверки на ошибки. Видите ли, inet_pton() возвращает -1 при ошибке и 0 если произошла какая-то путаница. Так что перед использованием убедитесь, что результат больше нуля Хорошо, теперь вы можете преобразовывать строковые IP адреса в их двоичное представление. Как насчёт других способов Что если у вас есть struct in_addr ивы хотите напечатать её в представлении цифр-и-точек? (Или struct in6_addr, которая нужна в, ух, “шестнадцатирично-с-двоеточием” представлении) В этом случае вам нужно воспользоваться функцией inet_ntop() (“ntop” означает “network to presentation” или можете называть “network to printable”, если так проще запомнить, как здесь // IPv4 ! char ip4[INET_ADDRSTRLEN]; // место для строки IPv4 struct sockaddr_in sa; // предположительно чем-то загружено ! inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN); ! printf(“The IPv4 address is: %s\n”, ip4); !! // IPv6 ! char ip6[INET6_ADDRSTRLEN]; // место для строки IPv6 struct sockaddr_in6 sa6; // предположительно чем-то загружено ! inet_ntop(AF_INET6, &(sa6.sin6_addr), ip6, INET6_ADDRSTRLEN); ! printf(“The IPv6 address is: %s\n”, ip6); 15 Beej's Guide to Network При вызове вы передаёте тип адреса (IPv4 или IPv6), адрес, указатель на строку для результата, максимальную длину этой строки. (Два макроса удобно определяют максимальный размер строки для адреса INET_ADDRSTRLEN и INET6_ADDRSTRLEN.) (Ещё одно быстрое замечание к упомянутым старым методам историческая функция для такого преобразования inet_ntoa(). Она также устарела и не работает с IPv6.) И напоследок, эти функции работают только с цифровыми IP адресами, ноне с именами хостов для DNS серверов, как “www.example.com”. Для этого надо использовать getaddrinfo(), но об этом позднее. Частные (или отключённые) сети Множество мест имеют брандмауэры, скрывающие сети от остального мира своей собственной защитой. Очень часто брандмауэр транслирует внутренние IP адреса во внешние (известные всему миру) IP адреса с помощью процесса именуемого Network Address Translation или NAT. Вы ещё нервничаете Куда он забрёл со всеми этими странными штуками Ладно, расслабьтесь и купите себе безалкогольный (или алкогольный) напиток, поскольку, как начинающему, вам ненужно беспокоиться об NAT, он для вас прозрачен. Ноя хотел поговорить о сетях за брандмауэром если вас начнут смущать увиденные сетевые номера. Например, у меня дома есть брандмауэр. Я имею два статичных IPv4 адреса, выделенных мне DSL компанией, и ещё семь компьютеров в сети. Как это возможно Два компьютера не могут иметь одинаковый адрес иначе данные не будут знать к какому им направляться Вот ответ они не разделяют один адрес. Они находятся в частной сети с выделенными для неё 24 миллионами IP адресов. Они все только для меня. Вот так, они все для меня и больше никого не касаются. Вот что происходит Если я вхожу в удалённый компьютер, он говорит мне, что я вошёл с 192.0.2.33, публичного IP, который мне выделил мой провайдер. Но если я спрашиваю у моего локального компьютера его адрес, он отвечает 10.0.0.5. Кто транслирует один IP адрес в другой Правильно, брандмауэр Это делает NAT! 10.x.x.x одна из немногих зарезервированных сетей, которые используются в полностью отключённых сетях, либо за брандмауэрами. Доступные вам номера частных сетей описаны в RFC 1918 , но наиболее часто вам встретятся 10.x.x.x и 192.168.x.x, где 16 x принимает значения от 0 до 255. Менее распространены 172.y.x.x, где y стоит между 16 и 31. Сетям за брандмауэром ненужно быть одной из этих зарезервированных сетей, но обычно таки есть. Забавный факт Мой внешний IP адрес в действительности не 192.0.2.33. Сеть 192.0.2.x зарезервирована в качестве воображаемых настоящих IP адресов для использования в документации, такой как это пособие) IPv6 до известной степени тоже имеет частные сети. Они будут начинаться с fdxx : или может быть в будущем fcxx:), как в RFC 4193 . NAT и IPv6 как правило не смешиваются, однако, (если только вы не делаете шлюз между IPv6 и IPv4, что лежит вне области рассмотрения этого документа) в теории у вас будет столько адресов, что NAT больше не понадобится. Но если вы хотите выделить для себя адреса в сети, которая не будет доступна снаружи, то вот как это делается. 16 http://tools.ietf.org/html/rfc1918 16 http://tools.ietf.org/html/rfc4193 17 Beej's Guide to Network Programming 4. Прыжок изв Ноя просто хотел сказать вам что изменить в моём коде, чтобы он работал с IPv6! Скажите Окей! Окей! Почти всё здесь я проходил ранее, но это краткая версия для нетерпеливых. (Конечно, то больше, чем это, зато это применимо в данном пособии) Прежде всего, попробуйте воспользоваться getaddrinfo(), чтобы получить всю информацию структуры sockaddr, вместо того, чтобы заполнять её вручную. Это сделает вас независимым от версии IP и устранит множество последующих шагов. Попробуйте любое написанное место, относящееся к версии IP, исполнить во вспомогательной функции. Измените на AF_INET6. Измените на PF_INET6. Измените INADDR_ANY на in6addr_any , что несколько отличается struct sockaddr_in sa; struct sockaddr_in6 sa6; sa.sin_addr.s_addr = INADDR_ANY; // используйте мой IPv4 адрес sa6.sin6_addr = in6addr_any; // используйте мой IPv6 адрес Также, используйте значение IN6ADDR_ANY_INIT как инициализатор при объявлении struct in6_addr : struct in6_addr ia6 = IN6ADDR_ANY_INIT; Вместо struct sockaddr_in используйте struct sockaddr_in6, обязательно добавив “6” в соответствующие поля (см. 3.3 Структуры выше. Поля sin6_zero нет. Вместо struct in_addr используйте, обязательно добавив “6” в соответствующие поля (см. struct-s выше. Вместо или inet_addr(), используйте inet_pton(). Вместо inet_ntoa(), используйте inet_ntop(). 10. Вместо gethostbyname(), используйте лучшую getaddrinfo(). 11. Вместо gethostbyaddr(), используйте лучшую хотя до сих пор работает с IPv6). 12. больше не работает. Используйте широковещание IPv6 ! Et voila! ! 17 Beej's Guide to Network Programming 5. Системные вызовы или Облом В этом разделе мы приступаем к системным вызовами вызовам других библиотек, которые позволяют вам достичь сетевых возможностей Unix или любых иных систем, поддерживающих этот API сокетов (BSD, Windows, Linux, Mac и что-там-у-вас). Когда вы вызываете одну из этих функций, ядро берет власть и выполняет всю работу за вас автомагически. Место, где большинство людей зависает, это порядок вызова. Как вы уже, наверное, обнаружили, man страницы бесполезны. Хорошо, чтобы помочь в этой ужасной ситуации, я попытался в последующих разделах расположить системные вызовы точно примерно) в том порядке, в каком к ним надо обращаться в ваших программах. Это, на парус несколькими кусками примеров кода здесь и там, немного молока с печеньем (которым, я боюсь, вам надо будет себя снабжать, немного сырых потрошков и мужества ивы будете излучать данные в Интернет как Сын Джона Постела! Пожалуйста, заметьте, что во многие отрывки кода для краткости не включена необходимая проверка на ошибки. Подразумевается, что вызов getaddrinfo() успешен и возвращена правильная ссылка на связанный список. Так что используйте их как модель, хотя в отдельных программах они применены правильно) 5.1. getaddrinfo() -‐ К старту -‐ товсь! Это настоящая рабочая лошадка функций со множеством опций, хотя использовать её очень просто. Крохотный кусочек истории. Обычно вам нужно было вызывать функцию gethostbyname() для DNS поиска. Затем вы вручную записывали полученную информацию в struct sockaddr_in и использовали её в вызовах. Слава Богу, этого больше не требуется. (И даже нежелательно, если вы хотите писать код, работающий и си с IPv6!) В наши продвинутые времена у вас есть функция getaddrinfo(), которая делает для вас много хорошего, включая поиск имён DNS и служб и сверх того заполняет нужные вам структуры Давайте взглянем #include #include #include // например, "www.example.com" или IP const char *service, // например, "http" или номер порта const struct addrinfo *hints, struct addrinfo **res); Вы передаёте этой функции три входных параметра иона возвращает указатель на связанный список результатов, res. Параметр node это имя или IP адрес хоста, к которому надо подключиться. Следующий параметр service может быть номером порта, вроде “80”, или именем отдельной службы (приведены вили файле /etc/services вашей 18 Unix машины) как “http”, “ftp”, “telnet”, “smtp” или любой другой. Наконец, параметр hints указывает на struct addrinfo, которую вы уже заполнили нужной информацией. Вот пример вызова если вы сервер, который хочет слушать порт 3490 вашего IP адреса. Заметим, что в действительности слушания или установки сети не происходит, просто заполняются структуры, которые мы используем позднее. 18 http://www.iana.org/assignments/port-‐numbers 18 Beej's Guide to Network Programming ! int status; struct addrinfo hints; struct addrinfo *servinfo; // укажет на результат ! memset(&hints, 0, sizeof hints); // очистка структуры hints.ai_family = AF_UNSPEC; // IPv4 либо IPv6 hints.ai_socktype = SOCK_STREAM; // потоковый сокет TCP hints.ai_flags = AI_PASSIVE; // записать мой IP для меня ! if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status)); exit(1); } ! // servinfo теперь указывает на связанный список из 1 или более struct addrinfo // ... работайте пока не исчезнет надобность в servinfo .... freeaddrinfo(servinfo); // освободить связанный список Заметьте, что я установил ai_family в AF_UNSPEC, указывая, что мне всё равно IPv4 или IPv6. Вы можете установить AF_INET или AF_INET6 если хотите использовать их отдельно. Также вы видите флаг AI_PASSIVE, он говорит getaddrinfo(), что структурам сокета нужно назначить адрес моего локального хоста. Это прекрасно, покольку отныне вам ненужно его жёстко определять. (Или вы можете специальный адрес в первый параметр getaddrinfo(), где у меня сейчас NULL.) Затем мы делаем вызов. Если есть ошибка (getaddrinfo() возвращает не-ноль), мы можем распечатать е, используя функцию gai_strerror(). Если всё работает правильно, servinfo будет указывать на связанный список структур struct addrinfo, каждая из которых содержит struct sockaddr определённого типа, которые мы можем использовать позднее Ловко В итоге, когда мы наконец-то закончим работать со связанным списком, который getaddrinfo() так любезно нам предоставила, мы можем (и должны) освободить всё это, вызвав freeaddrinfo(). Вот пример вызова если вы клиент, который хочет подсоединиться к определённому серверу, скажем, “www.example.net” порт 3490. Опять же это ненастоящее подключение, а заполнение структур, которые мы используем позднее int status; struct addrinfo hints; struct addrinfo *servinfo; // укажет на результат ! memset(&hints, 0, sizeof hints); // очистка структуры hints.ai_family = AF_UNSPEC; // IPv4 либо IPv6 hints.ai_socktype = SOCK_STREAM; // потоковый сокет TCP ! // готовьтесь к соединению status = getaddrinfo("www.example.net", "3490", &hints, &servinfo); ! // servinfo теперь указывает на связанный список из 1 или более struct addrinfo // и т.д. Я продолжаю говорить, что это связанный список со всеми видами адресной информации. Давайте напишем быструю демо программу вывода этой информации. Эта 19 Beej's Guide to Network программа будет печатать IP адрес любого хоста, который вы укажете в командной строке /* ** showip.c -- выводит IP адреса заданного в командной строке хоста */ ! #include #include #include #include #include #include #include ! int main(int argc, char *argv[]) { struct addrinfo hints, *res, *p; int status; char ipstr[INET6_ADDRSTRLEN]; if (argc != 2) { fprintf(stderr,"usage: showip hostname\n”); ! return 1; } ! memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // AF_INET или AF_INET6 если требуется hints.ai_socktype = SOCK_STREAM; if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); ! return 2; } ! printf("IP addresses for %s:\n\n", argv[1]); for(p = res;p != NULL; p = p->ai_next) { void *addr; char *ipver; // получить, // в IPv4 и IPv6 поля разные if (p->ai_family == AF_INET) { // IPv4 struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; addr = &(ipv4->sin_addr); ipver = "IPv4"; } else { // IPv6 ! struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr; addr = &(ipv6->sin6_addr); ipver = "IPv6"; } // перевести IP в строку и распечатать inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); printf(" %s: %s\n", ipver, ipstr); } freeaddrinfo(res); // освободить связанный список return 0; } |