Is sin_addr.s_addr = INADDR_ANY; нужен хтонл вообще? - PullRequest
33 голосов
/ 21 мая 2011

Я наткнулся на две темы:

Сокет с тайм-аутом recv: что не так с этим кодом?

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

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

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

Ответы [ 7 ]

34 голосов
/ 21 мая 2011

Так как другие константы, такие как 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, поэтому преобразование - это неоперация.

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

  1. Опытные программисты сокетов могут обидеть использование htonl, потому что они будут знать, что это ничего не делает (так как они знают значениепостоянная наизусть).
  2. Требуется меньше набирать текст, чтобы пропустить его.
  3. Ложная оптимизация «производительности» (ясно, что это не имеет значения).
17 голосов
/ 21 мая 2011

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 необходим htonl.

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

8 голосов
/ 21 мая 2011

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

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

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

3 голосов
/ 30 мая 2012

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

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

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

2 голосов
/ 13 октября 2013

Давайте подведем итоги немного, так как ни один из предыдущих ответов, кажется, не актуален, и я, возможно, не последний человек, который увидит эту страницу с вопросом. Было высказано мнений как за, так и против использования 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 (но иногда для локального хоста IPv6 или просто для любого имени хоста), getaddrinfo () (упоминается в одном из ответов) будет гораздо лучше, например:

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

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

    struct addrinfo * ai, hints = {.ai_family = AF_INET}; getaddrinfo (0, "1234", & hints, & 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() можетБудучи значительно медленнее, чем использование констант, системная библиотека - лучшее место, чтобы это исправить.Хорошая реализация будет просто возвращать запрошенный адрес обратной связи, когда service равно нулю и установлено hints.ai_family.

2 голосов
/ 16 июня 2011

Стивенс последовательно использует htonl(INADDR_ANY) в книге Сетевое программирование UNIX (моя копия с 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() во время компиляции.

Теоретически этобыло бы возможно для некоторого разработчика переопределить 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-битный интернет-порт в ноль.

0 голосов
/ 21 мая 2011

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

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 всегда О, так что конвертировать это нельзя, и я думаю (и надеюсь), что многие другие сопровождающие сделают то же самое ».

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

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

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

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

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

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

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

  1. Требуется меньше печатать, чтобы опустить его.

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

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

Трудно понять, откуда пришел этот аргумент.Это может быть попытка выдвинуть оппозицию глупые, казалось бы, аргументы.В любом случае отсутствие макроса 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, прежде чем касаться строки, которая его использует.

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