Подтвердить что ты не робот

Does sin_addr.s_addr = INADDR_ANY; нужен хтонн вообще?

Я наткнулся на два потока:

Socket with recv-timeout: что не так с этим кодом?

Чтение/запись в сокет с использованием потока FILE в c

используется htonl, а другой - нет.

Что правильно?

4b9b3361

Ответ 1

Так как другие константы, такие как INADDR_LOOPBACK, находятся в порядке байтов хоста, я утверждаю, что все константы в этом семействе должны иметь к ним htonl, включая INADDR_ANY.

(Примечание: я написал этот ответ, когда @Mat редактировал, теперь его ответ также говорит, что лучше быть последовательным и всегда использовать htonl.)

Обоснование

Это опасно для будущих сопровождающих вашего кода, если вы пишете это следующим образом:

if (some_condition)
    sa.s_addr = htonl(INADDR_LOOPBACK);
else
    sa.s_addr = INADDR_ANY;

Если бы я просматривал этот код, я бы сразу спросил, почему одна из констант имеет htonl, а другая - нет. И я бы сообщал об этом как об ошибке, независимо от того, было ли у меня "внутреннее знание", что INADDR_ANY всегда 0, поэтому преобразование это не-op.

Код, который вы пишете, заключается не только в том, что он имеет правильное поведение во время работы, но и там, где это возможно, также очевидно, и легко поверить, что он правильный. По этой причине вам не следует выделять htonl вокруг INADDR_ANY. Три причины не использовать htonl, которые я вижу:

  • Он может оскорбить опытных программистов сокета, чтобы использовать htonl, потому что они будут знать, что он ничего не делает (поскольку они знают значение константы наизусть).
  • Это требует меньше ввода, чтобы опустить его.
  • Оптимизация "производительности" (очевидно, это не имеет значения).

Ответ 2

INADDR_ANY - это "любой адрес" в IPV4. Этот адрес 0.0.0.0 в пунктирной нотации, поэтому 0x000000 в шестнадцатеричном виде по любой контуре. Прохождение через htonl не имеет эффекта.

Теперь, если вы хотите узнать о других макроконстантах, посмотрите INADDR_LOOPBACK, если он определен на вашей платформе. Скорее всего, это будет такой макрос:

#define INADDR_LOOPBACK     0x7f000001  /* 127.0.0.1   */

(из linux/in.h, эквивалентное определение в winsock.h).

Итак, для INADDR_LOOPBACK необходим a htonl.

Для обеспечения согласованности во всех случаях было бы лучше использовать htonl.

Ответ 3

Ни один из них не прав, в том смысле, что оба INADDR_ANY и htonl устарели и приводят к сложному, уродливому коду, который работает только с IPv4. Переключитесь на использование getaddrinfo для всех ваших нужд создания сокетов:

struct addrinfo *ai, hints = { .ai_flags = AI_PASSIVE|AI_ADDRCONFIG };
getaddrinfo(0, "1234", &hints, &ai);

Замените "1234" номером вашего порта или именем службы.

Ответ 4

Стивенс последовательно использует htonl(INADDR_ANY) в книге UNIX Network Programming (моя копия с 1990 года).

Текущая версия FreeBSD выпускает 12 INADDR_ констант в netinet/in.h; 9 из 12 требуют htonl() для правильной работы. (9 - это INADDR_LOOPBACK и 8 других групповых адресов групповой адреса, таких как INADDR_ALLHOSTS_GROUP и INADDR_ALLMDNS_GROUP.)

На практике не имеет значения, используете ли вы INADDR_ANY или htonl(INADDR_ANY), кроме возможного удара по производительности от htonl(). И даже это возможное поражение производительности может не существовать - с моим 64-битным gcc 4.2.1, включение любого уровня оптимизации вообще активирует преобразование констант htonl() compile-time.

В теории было бы возможно, чтобы какой-то разработчик переопределил INADDR_ANY к значению, где htonl() действительно что-то делает, но такое изменение нарушит десятки тысяч существующих фрагментов кода и не выживет в "реальный мир"... Существует слишком много кода, который явно или неявно зависит от INADDR_ANY, который определяется как некоторая нулевая ценность. Стивенс, вероятно, не собирался предполагать, что INADDR_ANY всегда равен нулю, когда писал:

cli_addr.sin_addr.s_addr = htonl(INADDR_ANY);
cli_addr.sin_port        = htons(0);

При назначении локального адреса для клиента, используя bind, мы устанавливаем Интернет-адрес INADDR_ANY и 16-разрядный интернет-порт до нуля.

Ответ 5

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

Я думаю, что из ответов и комментария здесь ясно, что для этих констант необходимо использовать htonl() (хотя вызов его на INADDR_ANY и INADDR_NONE эквивалентен no-ops). Проблема, которую я вижу в том, где возникает путаница, заключается в том, что она явно не вызвана в документации - кто-то, пожалуйста, исправьте меня, если я просто пропустил ее, но я не видел на страницах руководства или в заголовке include, где он явно указывает, что определения для INADDR_* находятся в порядке хозяина. Опять же, не очень важно для INADDR_ANY, INADDR_NONE и INADDR_BROADCAST, но это важно для INADDR_LOOPBACK.

Теперь я сделал довольно много работы на низкоуровневом соке в C, но адрес loopback редко, если вообще когда-либо, используется в моем коде. Хотя эта тема старше года, эта проблема просто подпрыгнула, чтобы укусить меня в сегодняшнем прошлом, и это было потому, что я ошибался, допуская, что адреса, определенные в заголовке include, находятся в сетевом порядке. Не знаю, почему у меня была эта идея - вероятно, потому, что структура in_addr должна иметь адрес в сетевом порядке, inet_aton и inet_addr возвращать свои значения в сетевом порядке, и поэтому мое логическое предположение заключалось в том, что эти константы будут полезны как есть. Бросив вместе быстрый 5-лайнер, чтобы проверить, что теория показала мне иначе. Если какая-либо из возможностей - вот что, должно быть, увидеть это, я бы сделал предложение явно указать, что значения, по сути, находятся в порядке хозяина, а не в порядке сети, и что к ним следует применить htonl(). Для обеспечения согласованности я также предложил бы, поскольку другие уже сделали это уже здесь, что htonl() будет использоваться для всех значений INADDR_*, даже если он ничего не делает для значения.

Ответ 6

Немного подведем итоги, так как ни один из предыдущих ответов не обновляется, и я не могу быть последним, кто увидит эту страницу вопросов. Имелись мнения как за, так и против использования htonl вокруг константы INADDR_ANY или вообще ее избегания.

В настоящее время (и в настоящее время уже довольно давно) системные библиотеки в основном готовятся к IPv6, поэтому мы используем IPv4, а также IPv6. Ситуация с IPv6 намного проще, поскольку структуры данных и константы не страдают от порядка байтов. Можно использовать "in6addr_any", а также "in6addr_loopback" (оба типа struct in6_addr), и оба они являются постоянными объектами в байтовом порядке сети.

Посмотрите, почему IPv6 не страдает от одной и той же проблемы (если адреса IPv4 были определены как четыре байтовых массива, они также не пострадали):

struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

struct in6_addr {
    unsigned char   s6_addr[16];   /* IPv6 address */
};

Для IPv4 было бы неплохо также иметь inaddr_any и inaddr_loopback как константы struct in_addr (чтобы их можно было сравнить с memcmp или скопировать с помощью memcpy). Действительно, может быть хорошей идеей создать их в вашей программе, поскольку они не предоставляются glibc и другими библиотеками:

const struct in_addr inaddr_loopback = { htonl(INADDR_LOOPBACK) };

С glibc это работает только для меня внутри функции (и я не могу сделать ее static), поскольку htonl не является макросом, а обычной функцией.

Проблема в том, что glibc (в отличие от того, что утверждалось в других ответах) не обеспечивает htonl как макрос, а скорее как функцию. Поэтому вам придется:

static const struct in_addr inaddr_any = { 0 };
#if BYTE_ORDER == BIG_ENDIAN
static const struct in_addr inaddr_loopback = { 0x7f000001 };
#elif BYTE_ORDER == LITTLE_ENDIAN
static const struct in_addr inaddr_loopback = { 0x0100007f };
#else
    #error Neither big endian nor little endian
#endif

Это было бы очень приятным дополнением к заголовкам, и тогда вы могли бы работать с константами IPv4 так же легко, как с IPv6.

Но для этого я должен был использовать некоторые константы для инициализации этого. Когда я точно знаю соответствующие байты, мне не нужны константы. Точно так же, как некоторые люди утверждают, что htonl() избыточно для константы, которая оценивается как нуль, кто-то еще может утверждать, что сама константа также избыточна. И он будет прав.

В коде я предпочитаю быть явным, чем неявным. Поэтому, если эти константы (например, INADDR_ANY, INADDR_ALL, INADDR_LOOPBACK) все последовательно находятся в порядке байтов хоста, тогда это только правильно, если вы относитесь к ним так. См. Например (если вы не используете указанную константу):

struct in_addr address4 = { htonl(use_loopback ? INADDR_LOOPBACK : INADDR_ANY };

Конечно, вы можете сказать, что вам не нужно вызывать htonl для INADDR_ANY, и поэтому вы можете:

struct in_addr address4 = { use_loopback ? htonl(INADDR_LOOPBACK) : INADDR_ANY };

Но тогда, игнорируя порядок байтов константы, так как в любом случае он равен нулю, я не вижу много логики в использовании константы вообще. То же самое относится к INADDR_ALL, так как легко также ввести 0xffffffff;

Другой способ обойти это - не устанавливать эти значения напрямую:

struct in_addr address4;

inet_pton(AF_INET, "127.0.0.1", &address4);

Это добавляет немного бесполезной обработки, но у нее нет проблем с порядком байтов, и она практически одинакова для IPv4 и IPv6 (вы просто меняете адресную строку).

Но вот почему вы делаете это вообще. Если вы хотите connect() на IPv4 localhost (но иногда на IPv6 localhost или просто на любое имя хоста), getaddrinfo() (упомянутый в одном из ответов) намного лучше для него:

  • Это функция, используемая для перевода любого имени хоста/службы/семейства/socktype/protocol a в список совпадающих записей struct addrinfo.

  • Каждый struct addrinfo содержит полиморфный указатель на struct sockaddr, который можно напрямую использовать с connect(). Поэтому вам не нужно заботиться о построении struct sockaddr_in, typecasting (через указатель) до struct sockaddr и т.д.

    struct addrinfo * ai, hints = {.ai_family = AF_INET}; getaddrinfo (0, "1234", & hairsp; & ai);

    которые, в свою очередь, содержат указатели полиморфных структур struct sockaddr, которые вам нужны для вызова connect().

Итак, вывод:

1) Стандартный API не может предоставить непосредственно используемые константы struct in_addr (вместо этого он предоставляет бесполезные беззнаковые целочисленные константы в порядке хоста).

struct addrinfo *ai, hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_TCP };
int error;

error = getaddrinfo(NULL, 80, &hints, &ai);
if (error)
    ...

for (item = result; item; item = item->ai_next) {
    sock = socket(item->ai_family, item->ai_socktype, item->ai_protocol);

    if (sock == -1)
        continue;

    if (connect(sock, item->ai_addr, item->ai_addrlen) != -1) {
        fprintf(stderr, "Connected successfully.");
        break;
    }

    close(sock);
}

Если вы уверены, что ваш запрос достаточно избирательный, что он возвращает только один результат, вы можете сделать (исключая обработку ошибок для краткости) следующее:

struct *result, hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_TCP };
getaddrinfo(NULL, 80, &hints, &ai);
sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
connect(sock, result->ai_addr, result->ai_addrlen);

Если вы боитесь, что getaddrinfo() может быть значительно медленнее, чем использование констант, системная библиотека - лучшее место для исправления. Хорошая реализация просто вернет запрошенный адрес loopback, когда service имеет значение null и hints.ai_family.

Ответ 7

Мне обычно не нравится отвечать, когда уже есть "достойный" ответ. В этом случае я собираюсь сделать исключение, потому что информация, которую я добавил к этим ответам, неверно истолкована.

INADDR_ANY определяется как адрес IPv4 с нулевым битом, 0.0.0.0 или 0x00000000. Вызов htonl() для этого значения приведет к тому же значению, нолю. Поэтому вызов htonl() в этом постоянном значении технически не требуется.

INADDR_ALL определяется как адрес IPv4 с одним битом, 255.255.255.255 или 0xFFFFFFFF. Вызов htonl() с помощью INADDR_ALL вернет INADDR_ALL. Опять же, вызов htonl() не является технически необходимым.

Другая константа, определенная в файлах заголовков, INADDR_LOOPBACK, определяемая как 127.0.0.1 или 0x7F000001. Этот адрес указан в порядке сетевого байта и не может быть передан в интерфейс сокетов без htonl(). Вы должны использовать htonl() с этой константой.

Некоторые предполагают, что согласованность и читаемость кода требуют, чтобы программисты использовали htonl() для любой константы с именем INADDR_* - потому что это необходимо для некоторых из них. Эти плакаты ошибочны.

Пример, приведенный в этом потоке:

if (some_condition)
    sa.s_addr = htonl(INADDR_LOOPBACK);
else
    sa.s_addr = INADDR_ANY;

Цитата из "Джон Звинк":

"Если бы я просматривал этот код, я бы сразу спросил, почему одна из констант применяет htonl, а другая - нет. И я сообщаю об этом как об ошибке, независимо от того, было ли у меня" внутреннее знание ", что INADDR_ANY всегда 0, поэтому преобразование это не-op. И я думаю (и надеюсь), что многие другие сторонники будут делать то же самое".

Если бы я получал такой отчет об ошибке, я бы сразу его выбросил. Этот процесс мог бы сэкономить мне много времени, выдавая сообщения об ошибках от людей, у которых нет "базового минимального знания", что INADDR_ANY всегда равно 0. (Предполагая, что знание значений INADDR_ANY и др. Каким-то образом нарушает инкапсуляцию или что-то другое не стартер - те же номера используются в выводе netcat и внутри ядра. Программистам необходимо знать фактические числовые значения. Люди, которые не знают, не испытывают недостатка в знаниях, им не хватает базовые знания в области.)

Действительно, если у вас есть программатор, поддерживающий код сокетов, и этот программист не знает битовые паттерны INADDR_ANY и INADDR_ALL, у вас уже проблемы. Обертка 0 в макросе, который возвращает 0, является видом менталитета, который является подчиненным к бессмысленной консистенции и не уважает знание домена.

Поддержание кода сокетов - это нечто большее, чем понимание C. Если вы не понимаете разницу между INADDR_LOOPBACK и INADDR_ANY на уровне, совместимом с выходом netstat, тогда вы опасны в этом коде и не должны измените его.

Сословные аргументы, предложенные Звинком относительно ненужного использования htonl():

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

Это аргумент соломы, потому что у нас есть представление о том, что опытные программисты сокетов знают наизусть значение INADDR_ANY. Это похоже на то, что только опытный программист С знает наизусть значение NULL. Написание "наизусть" создает впечатление, что число трудно запомнить, возможно, несколько цифр, например 127.0.0.1. Но нет, мы гиперболически обсуждаем сложность запоминания паттернов с именем "все нулевые биты" и "все одни биты".

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

  • Это требует меньше ввода, чтобы опустить его.

Этот аргумент должен быть абсурдным и пренебрежительным, поэтому ему не нужно много опровергать.

  • Оптимизация "производительности" (очевидно, это не имеет значения).

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


Причиной не использовать htonl() с INADDR_ANY является то, что самый опытный программист сокетов знает, что он не нужен. Что еще: те программисты, которые не знают, должны учиться. Нет дополнительной "стоимости" с использованием htonl(), проблема связана с установлением стандарта кодирования, который способствует незнанию таких критически важных значений.

По определению инкапсуляция способствует незнанию. Такое незнание - обычное преимущество использования инкапсулированного интерфейса - знание дорого и конечно, поэтому инкапсуляция обычно хороша. Возникает вопрос: какие усилия программирования лучше всего улучшить с помощью инкапсуляции? Существуют ли задачи программирования, которые разрешаются путем инкапсуляции?

Технически неверно использовать htonl(), поскольку он не влияет на это значение. Однако аргументы, которые вы должны использовать, могут вводить в заблуждение.

Есть те, кто будет утверждать, что лучшей ситуацией будет то, что разработчику не нужно было знать, что INADDR_ANY - все нули и т.д. Эта страна невежества хуже, а не лучше. Считайте, что эти "магические значения" используются на разных интерфейсах с TCP/IP. Например, при настройке Apache, если вы хотите прослушивать только IPv4 (а не IPv6), вы должны указать:

Listen 0.0.0.0:80

Я столкнулся с программистами, которые ошибочно предоставили локальный IP-адрес вместо INADDR_ANY (0.0.0.0) выше. Эти программисты не знают, что такое INADDR_ANY, и они, вероятно, завершают его в htonl(), пока они на нем. Это земля мышления абстракции и инкапсуляции.

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


Моя точка зрения такова: "нет" правильного использования INADDR_ANY с htonl() - оба эквивалентны. Я бы не рекомендовал принять требование о том, чтобы значение использовалось каким-либо определенным образом, поскольку семейство констант INADDR_X имеет только четыре члена, и только один из них INADDR_LOOPBACK имеет значение, которое отличается в зависимости от порядка байтов. Лучше просто знать этот факт, чем устанавливать стандарт для использования значений, которые превращают "слепой глаз" в битовые шаблоны значений.

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

При программировании на C, особенно, большинство "использования" API сокетов включает в себя захват другого исходного кода и адаптацию его. Это еще одна причина, по которой так важно знать, что INADDR_ANY до касания строки, которая ее использует.