Как работает этот код из примеров "Сетевое программирование"? - PullRequest
1 голос
/ 18 февраля 2011

Я читаю Биджа " Руководство по сетевому программированию ".

В одном из своих вступительных примеров он рассказывает о получении IP-адреса для имени хоста (например, google.com или yahoo.com). Вот код.

/*
** showip.c -- show IP addresses for a host given on the command line
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

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 or AF_INET6 to force version
    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;

        // get the pointer to the address itself,
        // different fields in IPv4 and 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";
        }

        // convert the IP to a string and print it:
        inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
        printf("  %s: %s\n", ipver, ipstr);
    }

    freeaddrinfo(res); // free the linked list

    return 0;
}

Меня смущает цикл for.

for(p = res; p != NULL; p = p->ai_next) {
    void *addr;
    char *ipver;

    // get the pointer to the address itself,
    // different fields in IPv4 and 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";
    }

    // convert the IP to a string and print it:
    inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
    printf("  %s: %s\n", ipver, ipstr);
}

Кто-нибудь возражал бы пройти через псевдо-шаг за шагом, что происходит или что это такое? Итерирует ли он по связанному списку? .. У меня есть общее представление о том, что такое struct addrinfo, но что за черт это struct *res и struct *p или void *addr и *char ipversion.

Ответы [ 3 ]

5 голосов
/ 18 февраля 2011

Перво-наперво, знаете, что такое связанный список ? Если вы понимаете это, вы узнаете, что происходит с циклом for. p - указатель на структуру, которая также ссылается ( links ) на следующую структуру в списке. Итак, вы просматриваете список тех структур, которые являются addrinfo структурами. 4

Теперь, что вам нужно знать о сетевых пакетах, так это то, что они состоят из заголовка. В частности, Ethernet frame . Это аппаратный протокол. Он позволяет вам работать в физической ограниченной сети, но ничего не знает о маршрутизации через границы физической сети.

Далее идет tcp или, возможно, другой протокол транспортного уровня, который находится где-то между двумя уровнями. TCP по сравнению с UDP и X по поводу того, как вы управляете пакетами - например, TCP требует, чтобы пакеты были собраны по порядку, в то время как UDP является протоколом «широковещательного» типа.

Наконец, у вас есть набор интернет-протоколов (IPv4, IPv6). Это протоколы более высокого уровня, которые контролируют более широкое понимание маршрутизации, поэтому они знают об Интернете в целом, но меньше знают о шагах, необходимых для его достижения.

Отличным объяснением этого является удобная диаграмма на этой странице . Чтобы завершить картину, BGP - это то, как маршрутизаторы знают, как перемещать вещи.

tcp / udp вписывается в эту картину, будучи частью (воплощенного в) рассматриваемого протокола (например, IPv4)

Таким образом, фреймы Ethernet содержат другие протоколы, прежде всего IPv4, которые содержат информацию, необходимую маршрутизаторам для передачи его через Интернет (через несколько физических сетей). Протокол internet указывает, куда вы хотите отправиться, откуда вы находитесь. Таким образом, тело типичного IPv4 остается неизменным на протяжении всего его транзита, но каждый раз, когда он пересекает физические сети, он упаковывается в другой пакет Ethernet.

Теперь в заголовке ethernet есть поле для поиска того, что содержит "тело ethernet". Эта строка:

 if (p->ai_family == AF_INET) {

ли. AF_INET - это константа, которая соответствует значению, которое tcp использует для идентификации тела пакета как IPv4. Итак, если вы смотрите на заголовок IPv4, этот цикл затем продолжает читать эту информацию.

Условие else является технически неправильным, потому что отсутствие IPv4 не делает его автоматически IPv6. Вы можете изменить его для проверки IPv6 следующим образом:

 else if (p->ai_family == AF_INET6) { 

Что вы, возможно, захотите сделать, на случай, если вы подберете что-нибудь еще.

Теперь стоит объяснить немного магии:

struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;

Это в основном принимает сетевую, или необработанную, форму данных, которые отображаются в виде последовательности байтов, и преобразует их (покрывает их) в поля структуры. Поскольку вы знаете, насколько большими будут поля, это очень быстрый и простой способ извлечь то, что вам нужно.

Последнее, что требует объяснения, это:

inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);

Есть и другие способы достижения этой цели, в частности ntohs().

В основном данные сети передаются в кодировке с прямым порядком байтов, и для их чтения необходимо (потенциально) преобразовать данные в кодировку вашей системы. Это может быть большой порядок байтов, или он может быть небольшим, это зависит от вашей системы по большей части. Прочитайте статью в Википедии о endianness .

Резюме: здесь вы видите комбинацию структур информатики, работы сетей и кода С.

1 голос
/ 18 февраля 2011

Ну, это не так сложно. getaddrinfo возвращает связанный список структур addrinfo (struct addrinfo **res на странице руководства), где каждая из этих структур содержит информацию об одном адресе, доступном для данного интерфейса (const char *node на странице руководства).

Теперь, каждая структура проверяется, и информация о структуре печатается. Чтобы распечатать IPv4 или IPv6 , переменная ipver устанавливается соответствующим образом. Перед распечаткой информации адрес должен быть преобразован из двоичной формы в строку. Это делается с помощью inet_ntop (* n * umber до * p * ointer).

Полученные строки inet_ntop (ipstr) и ipver теперь выводятся на консоль. Однако печатать ipver необязательно, поскольку вы узнаете тип адреса из ipstr: адрес IPv4 (как мы все знаем) записывается 192.168.1.10, тогда как адреса IPv6 используют двоеточия для разделения элементов адреса: 2001:0db8:85a3:0000:0000:8a2e:0370:7334.

0 голосов
/ 18 февраля 2011

Да, res указывает на связанный список addrinfo структур, которые представляют разные IP-адреса хоста. Документация MSDN о функции getaddrinfo довольно хороша. Я не знаю, на какой платформе вы работаете, но на других платформах она не должна сильно отличаться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...