getaddrinfo и INADDR_ANY

Потратил пару часов на поиски, все еще озадачен. Из того, что я нашел, INADDR_ANY предназначен для указания того, что сокет будет принимать соединения с любым адресом, назначенным серверу. Однако следующее приводит к тому, что клиент может подключиться к localhost:7777 только с того же компьютера.

addrinfo hints;
addrinfo* result;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
getaddrinfo(INADDR_ANY, "7777", &hints, &result);

SOCKET listenSocket = INVALID_SOCKET;
listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);

bind(listenSocket, result->ai_addr, (int)result->ai_addrlen);

Единственное решение, которое я нашел, это изменить INADDR_ANY на локальный IP-адрес машины:

getaddrinfo("192.168.0.105", "7777", &hints, &result);

Мне нужно понять, как работает INADDR_ANY, потому что мне кажется, что я просто неправильно его использую. Любая помощь будет оценена по достоинству.


person Raimonds Rainskis    schedule 08.10.2017    source источник
comment
INADDR_ANY имеет ожидаемый эффект, когда он передается в качестве параметра непосредственно в bind(). Вы этого не делаете. Вы передаете это значение в getaddrinfo(). Это совсем другая история. Теперь, почему бы вам не изучить, что getaddrinfo()возвращает в этом случае, и не выяснить это самостоятельно. Если вы хотите принимать соединения с любого локального IP-адреса, вам не нужен getaddrinfo(). Избавьтесь от всего этого целиком и вызовите bind() напрямую.   -  person Sam Varshavchik    schedule 08.10.2017
comment
Откуда вы вообще знаете, что getaddrinfo() вообще сработало? Вы не удосужились проверить возвращаемое значение. В соответствии с стандартом POSIX: нулевое возвращаемое значение для getaddrinfo() указывает на успешное завершение. ; ненулевое возвращаемое значение указывает на сбой. Возможные значения ошибок перечислены в разделе ОШИБКИ.   -  person Andrew Henle    schedule 08.10.2017
comment
getaddrinfo может возвращать несколько адресов; и вам также нужно freeaddrinfo(result), но вы всегда связываете только первый.   -  person Antti Haapala    schedule 08.10.2017
comment
Я пропустил проверку ошибок для краткости, мой плохой.   -  person Raimonds Rainskis    schedule 09.10.2017
comment
@SamVarshavchik Извините, я немного запутался: как мы можем пропустить getaddrinfo и напрямую использовать bind()? например, если да, то как мы узнаем адрес для привязки?   -  person Crystina    schedule 24.09.2020
comment
Вы это знаете, @Crystina, потому что это то, что вы прямо указываете bind(). Дополнительную информацию см. в документации bind(). Вы указываете INADDR_ANY в качестве адреса для привязки. Конец.   -  person Sam Varshavchik    schedule 24.09.2020


Ответы (1)


INADDR_ANY не имеет отношения к вашей проблеме.

Первый параметр getaddrinfo() — это const char *, указывающий IP-адрес или имя хоста. Но вместо этого INADDR_ANY является целым числом. Единственная причина, по которой ваш код даже компилируется, заключается в том, что INADDR_ANY определено как целочисленная константа 0, которая является единственной целочисленной константой, которую разрешено присваивать указателю. Итак, вы фактически передаете NULL первому параметру. Что хорошо в данной ситуации, так как это то, что вам в любом случае нужно.

Что вы не принимаете во внимание, так это то, что getaddrinfo() возвращает связанный список адресов, который может содержать несколько адресов, особенно, если вы используете AF_UNSPEC. Использование этого семейства сообщает getaddrinfo(), что оно может возвращать адреса для как IPv4, так и IPv6. Но вы используете только первый адрес в списке, который как раз соответствует localhost (127.0.0.1 в IPv4, ::1 в IPv6).

Для сервера вы должны создать и привязать отдельный прослушивающий сокет для каждого адреса в выходном списке. Это позволит вам привязаться ко всем адресам, которые соответствуют критериям, которые вы передали getaddrinfo(), например:

addrinfo hints = {};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;

addrinfo* result;

if (getaddrinfo(NULL, "7777", &hints, &result) == 0)
{
    for (addrinfo *addr = result; addr != NULL; addr = addr->ai_next)
    {
        SOCKET listenSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (listenSocket != INVALID_SOCKET)
        {
            bind(listenSocket, addr->ai_addr, (int)addr->ai_addrlen);
            listen(listenSocket, ...);
            // store listenSocket in a list for later use... 
        }
    }
    freeaddrinfo(result);
}

// use listening sockets as needed... 

В качестве альтернативы, как Сэм Ви упомянул в комментариях, вы можете пропустить getaddrinfo(), это не очень поможет вам в этой ситуации. Вы можете просто создать и связать 2 сокета напрямую, один для IPv4 и один для IPv6:

SOCKET listenSocket4 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket4 != INVALID_SOCKET)
{
    sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(7777);
    addr.sin_addr.s_addr = INADDR_ANY;

    bind(listenSocket4, (sockaddr*) &addr, sizeof(addr));
    listen(listenSocket4, ...);
}

SOCKET listenSocket6 = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket6 != INVALID_SOCKET)
{
    sockaddr_in6 addr = {};
    addr.sin6_family = AF_INET6;
    addr.sin6_port = htons(7777);
    addr.sin6_addr = in6addr_any;

    bind(listenSocket6, (sockaddr*) &addr, sizeof(addr));
    listen(listenSocket6, ...);
}

// use listening sockets as needed... 

Или лучше создайте и привяжите один двойной стек сокет, который поддерживает оба IPv4 и IPv6:

SOCKET listenSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket != INVALID_SOCKET)
{
    BOOL off = FALSE;
    setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&off, sizeof(off));

    sockaddr_in6 addr = {};
    addr.sin6_family = AF_INET6;
    addr.sin6_port = htons(7777);
    addr.sin6_addr = in6addr_any;

    bind(listenSocket, (sockaddr*) &addr, sizeof(addr));
    listen(listenSocket, ...);
}

// use listening socket as needed... 
person Remy Lebeau    schedule 08.10.2017
comment
Спасибо, последние 2 варианта - это то, что я искал. Руководства, которые мне удалось найти, были старыми и узкими по своему охвату, и это был первый раз, когда я пишу сетевой код без фреймворка, чтобы помочь мне, и я ничего не знал о том, как должен работать API. - person Raimonds Rainskis; 09.10.2017