Вопрос о INADDR_ANY

Константа INADDR_ANY — это так называемый групповой адрес IPv4. Подстановочный IP-адрес полезен для приложений, которые связывают сокеты интернет-домена на многосетевых хостах. Если приложение на многосетевом хосте привязывает сокет только к одному из IP-адресов своего хоста, то этот сокет может получать только дейтаграммы UDP или запросы на подключение TCP, отправленные на этот IP-адрес. Однако обычно мы хотим, чтобы приложение на многосетевом хосте могло получать дейтаграммы или запросы на подключение, в которых указывается любой из IP-адресов хоста, и привязка сокета к подстановочному IP-адресу делает это возможным.

struct sockaddr_in server_address;
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&server_address, 0, sizeof(struct sockaddr_in));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY); // here is my quesion
server_address.sin_port = htons(9734);

bind(server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address));

Вопрос>

Если мы привязываем сокет к определенному IP-адресу, то сокет может получать только запросы UPD/TCP, отправленные на этот IP-адрес.

Как показано в приведенном выше коде, теперь сокет server_sockfd связан с INADDR_ANY. Я просто чувствую себя сбитым с толку здесь, потому что, если сокет может получать любой запрос в Интернете, как он все еще может работать хорошо. В Интернете есть тонны запросов UDP / TCP, если сокет отвечает всем, как он все еще может работать?

// обновленный код для клиентской части //

int
main(int argc, char *argv[])
{
    struct sockaddr_in6 svaddr;
    int sfd, j;
    size_t msgLen;
    ssize_t numBytes;
    char resp[BUF_SIZE];

    if (argc < 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s host-address msg...\n", argv[0]);

    /* Create a datagram socket; send to an address in the IPv6 somain */

    sfd = socket(AF_INET6, SOCK_DGRAM, 0);      /* Create client socket */
    if (sfd == -1)
        errExit("socket");

    memset(&svaddr, 0, sizeof(struct sockaddr_in6));
    svaddr.sin6_family = AF_INET6;
    svaddr.sin6_port = htons(PORT_NUM);
    if (inet_pton(AF_INET6, argv[1], &svaddr.sin6_addr) <= 0)
        fatal("inet_pton failed for address '%s'", argv[1]);

    /* Send messages to server; echo responses on stdout */

    for (j = 2; j < argc; j++) {
        msgLen = strlen(argv[j]);
        if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
                    sizeof(struct sockaddr_in6)) != msgLen)
            fatal("sendto");

        numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
        if (numBytes == -1)
            errExit("recvfrom");

        printf("Response %d: %.*s\n", j - 1, (int) numBytes, resp);
    }

    exit(EXIT_SUCCESS);
}

// обновлено для кода на стороне сервера

int
main(int argc, char *argv[])
{
    struct sockaddr_in6 svaddr, claddr;
    int sfd, j;
    ssize_t numBytes;
    socklen_t len;
    char buf[BUF_SIZE];
    char claddrStr[INET6_ADDRSTRLEN];

    /* Create a datagram socket bound to an address in the IPv6 somain */

    sfd = socket(AF_INET6, SOCK_DGRAM, 0);
    if (sfd == -1)
        errExit("socket");

    memset(&svaddr, 0, sizeof(struct sockaddr_in6));
    svaddr.sin6_family = AF_INET6;
    svaddr.sin6_addr = in6addr_any;                     /* Wildcard address */
    svaddr.sin6_port = htons(PORT_NUM);

    if (bind(sfd, (struct sockaddr *) &svaddr,
                sizeof(struct sockaddr_in6)) == -1)
        errExit("bind");

    /* Receive messages, convert to uppercase, and return to client */

    for (;;) {
        len = sizeof(struct sockaddr_in6);
        numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
                            (struct sockaddr *) &claddr, &len);
        if (numBytes == -1)
            errExit("recvfrom");

        /* Display address of client that sent the message */

        if (inet_ntop(AF_INET6, &claddr.sin6_addr, claddrStr,
                    INET6_ADDRSTRLEN) == NULL)
            printf("Couldn't convert client address to string\n");
        else
            printf("Server received %ld bytes from (%s, %u)\n",
                    (long) numBytes, claddrStr, ntohs(claddr.sin6_port));

        for (j = 0; j < numBytes; j++)
            buf[j] = toupper((unsigned char) buf[j]);

        if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) !=
                numBytes)
            fatal("sendto");
    }
}

// обновлено для того, как запускать эти серверные/клиентские программы.

$ ./server_program &
[1] 31047
$ ./client_program ::1 ciao // Send to server on local host
Server received 4 bytes from (::1, 32770)
Response 1: CIAO

person q0987    schedule 01.08.2011    source источник


Ответы (1)


Он не получает запросы для каждого IP-адреса в Интернете(a), он получает запросы для каждого IP-адреса, который он обслуживает. Например, у него может быть несколько сетевых адаптеров, каждый с отдельным IP-адресом, или у него может быть один сетевой адаптер, способный управлять несколькими IP-адресами (у него может быть даже несколько сетевых адаптеров, каждый из которых может обрабатывать несколько IP-адресов).

Ключевой фрагмент, на который стоит обратить внимание:

... обычно мы хотим, чтобы приложение на многосетевом хосте могло получать дейтаграммы или запросы на подключение, в которых указан любой из IP-адресов хоста (курсив мой).

Другими словами, у вас может быть многосетевая конфигурация, в которой ваша машина обслуживает 10.0.0.15 и 10.0.0.16. Использование INADDR_ANY позволит вам собирать трафик для обоих этих адресов, не собирая запросы для 10.0.0.17, которым может быть машина на другом конце скамейки (или на другой стороне планеты).

В следующей таблице, где верхняя строка — адресаты запроса, а левый столбец — адрес, который вы слушаете, показано, будет ли вам передан запрос (Y) или нет (N):

Request to>  10.0.0.15  10.0.0.16  10.0.0.17
Bind to:    *-------------------------------
10.0.0.15   |    Y          N          N
10.0.0.16   |    N          Y          N
INADDR_ANY  |    Y          Y          N

(a) Он даже не видит подавляющее большинство запросов в сети. Подавляющее большинство даже не доходят до ближайшего маршрутизатора (или, возможно, даже до вашего интернет-провайдера). Даже те, которые делают добираются до ближайшего маршрутизатора, ваша конкретная машина может не увидеть, предназначены ли они для другой машины в локальном сегменте (несмотря на неразборчивый режим).

person paxdiablo    schedule 01.08.2011
comment
@ q0987 Сетевой стек операционной системы не будет доставлять IP-трафик, не предназначенный для одного из адресов этой машины, программам, если только не запрошен какой-либо диагностический режим (например, анализ пакетов). Довольно часто соответствующая адресация пакетов на более низком уровне и/или в коммутируемых сетях предотвращает попадание большей части трафика с неправильным адресом даже на уровень IP сетевого стека. - person Chris Stratton; 01.08.2011
comment
См. обновленный код на стороне клиента. Код на стороне клиента не указывает IP-адрес сервера, в вашем случае он не указывает IP-адрес в диапазоне от 10.0.0.15 до 10.0.0.16. Тогда как сервер узнает, что клиент отправляет ему запрос? - person q0987; 01.08.2011
comment
@ q0987 это может помочь вам узнать о сетевом стеке. Каждая сетевая машина может иметь один или несколько IP-адресов. Он будет реагировать только на трафик, направленный на один из этих IP-адресов. Магический адрес 0.0.0.0 просто означает любой из IP-адресов, за которые отвечает эта машина. - person Chris Eberle; 01.08.2011
comment
@ q0987, каждый IP-пакет имеет в заголовке IP-адреса источника и получателя (независимо от того, что делает ваш клиентский код). Это то, что делается под вашим клиентским кодом стеком TCP/IP. Следовательно, известно, что каждый прибывающий пакет поступает из определенного источника и в определенное место назначения (один из возможных IP-адресов, которые обслуживает эта машина). - person paxdiablo; 01.08.2011
comment
@paxdiablo, спасибо за красивую диаграмму. Вы заставили меня понять часть вопроса. Теперь другая часть, которую я все еще не понимаю, заключается в том, почему сервер может получить ответ от клиентской программы, которая не указывает IP-адрес сервера? - person q0987; 01.08.2011
comment
@ q0987 сокет-сервер не может получать трафик от клиента, который не указывает IP-адрес для отправки (хотя можно указать широковещательный адрес). Точно так же сервер должен указать адрес для получения, но этот адрес может быть любым из адресов этого компьютера. - person Chris Stratton; 01.08.2011
comment
@paxdiablo, см. команду, используемую для запуска сервера/клиента, в моем обновленном посте. -- Спасибо - person q0987; 01.08.2011
comment
@q0987, ваш клиентский код действительно указывает адрес сервера с inet_pton на argv[1]. Этот вызов принимает формат представления, такой как 10.0.0.15, и преобразует его в двоичный сетевой адрес. Другими словами, он передается в командной строке. См. kernel.org/doc/man-pages. /онлайн/страницы/man3/inet_pton.3.html - person paxdiablo; 01.08.2011
comment
@paxdiablo, причина того, что сервер может получать сообщения клиента, даже если клиент не указывает IP-адрес сервера, заключается в том, что они используют локальный хост в качестве канала связи. Это правильно? - person q0987; 01.08.2011
comment
@paxdiablo, как я уже упоминал выше, тестовый запуск не указывает IP-адрес сервера, вместо этого он просто использует локальный хост. - person q0987; 01.08.2011
comment
@q0987: не совсем так. ::1 является IP-адресом, и вы указываете его. Это IPv6-эквивалент IPv4 127.0.0.1. Адрес обратной связи (localhost) удобен для обозначения текущего хоста, но он так же действителен, как и любой другой IP-адрес. Почти наверняка это один из IP-адресов, который будет обслуживаться при указании INADDR_ANY. - person paxdiablo; 01.08.2011