sin_addr.s_addr = INADDR_ANY; 是否到底需要 htonl 吗?

发布于 2024-11-09 01:11:16 字数 355 浏览 0 评论 0原文

我遇到了两个线程:

带有recv超时的套接字:这段代码有什么问题?

在c中使用FILE流读取/写入套接字

一个使用 htonl 而另一个则不使用。

哪个是对的?

I came across two threads:

Socket with recv-timeout: What is wrong with this code?

Reading / Writing to a socket using a FILE stream in c

one uses htonl and the other doesn't.

Which is right?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

等风来 2024-11-16 01:11:16

由于诸如 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,因此转换它是无操作的。

您编写的代码不仅要具有正确的运行时行为,还应该尽可能明显并且易于相信它是正确的。因此,您不应删除 INADDR_ANY 周围的 htonl。我认为不使用 htonl 的三个原因是:

  1. 使用 htonl 可能会冒犯经验丰富的套接字程序员,因为他们知道它什么也不做(因为他们知道心中的常数)。
  2. 省略它需要更少的输入。
  3. 虚假的“性能”优化(显然这并不重要)。

Since other constants like INADDR_LOOPBACK are in host byte order, I submit that all the constants in this family should have htonl applied to them, including INADDR_ANY.

(Note: I wrote this answer while @Mat was editing; his answer now also says it's better to be consistent and always use htonl.)

Rationale

It is a hazard to future maintainers of your code if you write it like this:

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

If I were reviewing this code, I would immediately question why one of the constants has htonl applied and the other does not. And I would report it as a bug, whether or not I happened to have the "inside knowledge" that INADDR_ANY is always 0 so converting it is a no-op.

The code you write is not only about having the correct runtime behavior, it should also be obvious where possible and easy to believe it is correct. For this reason you should not strip out the htonl around INADDR_ANY. The three reasons for not using htonl that I can see are:

  1. It may offend experienced socket programmers to use htonl because they will know it does nothing (since they know the value of the constant by heart).
  2. It requires less typing to omit it.
  3. A bogus "performance" optimization (clearly it won't matter).
盛夏尉蓝 2024-11-16 01:11:16

INADDR_ANY 是 IPV4 中的“任意地址”。该地址采用点分符号表示为 0.0.0.0,因此在任何字节序上都采用十六进制表示为 0x000000。通过 htonl 传递它没有任何效果。

现在,如果您想了解其他宏常量,请查看 INADDR_LOOPBACK(如果您的平台上已定义)。它很可能是这样的宏:(

#define INADDR_LOOPBACK     0x7f000001  /* 127.0.0.1   */

来自linux/in.hwinsock.h中的等效定义)。

因此对于 INADDR_LOOPBACK 来说,一个 htonl 是必要的。

为了保持一致性,在所有情况下最好使用 htonl

INADDR_ANY is the "any address" in IPV4. That address is 0.0.0.0 in dotted notation, so 0x000000 in hex on any endianness. Passing it through htonl has no effect.

Now if you want to wonder about other macro constants, look at INADDR_LOOPBACK if it's defined on your platform. Chances are it will be a macro like this:

#define INADDR_LOOPBACK     0x7f000001  /* 127.0.0.1   */

(from linux/in.h, equivalent definition in winsock.h).

So for INADDR_LOOPBACK, an htonl is necessary.

For consistency, it could thus be better to use htonl in all cases.

放我走吧 2024-11-16 01:11:16

两者都不正确,因为 INADDR_ANYhtonl 都已被弃用,并导致复杂、丑陋的代码只能在 IPv4 上运行。切换到使用 getaddrinfo 来满足所有套接字地址创建需求:

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

"1234" 替换为您的端口号或服务名称。

Neither is right, in the sense that both INADDR_ANY and htonl are deprecated, and lead to complex, ugly code that only works with IPv4. Switch to using getaddrinfo for all of your socket address creation needs:

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

Replace "1234" with your port number or service name.

鸠书 2024-11-16 01:11:16

史蒂文斯在UNIX网络编程一书中一致使用htonl(INADDR_ANY)(我的副本是1990年的)。

FreeBSD 当前发行版本在 netinet/in.h 中定义了 12 个 INADDR_ 常量; 12 个中的 9 个需要 htonl() 才能正常运行。 (这 9 个是 INADDR_LOOPBACK 和其他 8 个多播组地址,例如 INADDR_ALLHOSTS_GROUPINADDR_ALLMDNS_GROUP。)

在实践中,是否使用 < code>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 位 Internet 端口清零。

Stevens uses htonl(INADDR_ANY) consistently in the book UNIX Network Programming (my copy is from 1990).

The current release version of FreeBSD defines 12 INADDR_ constants in netinet/in.h; 9 of the 12 require htonl() for proper functionality. (The 9 are INADDR_LOOPBACK and 8 other multicast group addresses such as INADDR_ALLHOSTS_GROUP and INADDR_ALLMDNS_GROUP.)

In practice, it makes no difference whether you use INADDR_ANY or htonl(INADDR_ANY), other than the possible performance hit from htonl(). And even that possible performance hit may not exist -- with my 64-bit gcc 4.2.1, turning on any level of optimization at all seems to activate compile-time htonl() conversion of constants.

In theory it would be possible for some implementer to redefine INADDR_ANY to a value where htonl() actually does something, but such a change would break tens of thousands of existing pieces of code out there and wouldn't survive in the "real world"... Too much code exists which depends explicitly or implicitly on INADDR_ANY being defined as some sort of zero-valued integer. Stevens likely didn't intend for anyone to assume that INADDR_ANY is always zero when he wrote:

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

In assigning a local address for the
client using bind, we set the
Internet address to INADDR_ANY and
the 16-bit Internet port to zero.

温柔少女心 2024-11-16 01:11:16

本来打算将其添加为注释,但它有点啰嗦...

我认为从答案和注释中可以清楚地看出,需要对这些常量使用 htonl() (尽管在 INADDR_ANYINADDR_NONE 上调用它相当于无操作)。我认为出现混淆的问题是它没有在文档中明确指出 - 如果我只是错过了它,请有人纠正我,但我没有在手册页中看到,也没有在包含标头中明确指出声明 INADDR_* 的定义按主机顺序排列。同样,对于 INADDR_ANYINADDR_NONEINADDR_BROADCAST 来说没什么大不了的,但对于 来说却很重要 INADDR_LOOPBACK

现在,我已经用 C 语言完成了相当多的低级套接字工作,但是环回地址很少(如果有的话)在我的代码中使用。虽然这个话题已经有一年多了,但这个问题今天突然出现并咬住了我,这是因为我错误地假设包含头中定义的地址是按网络顺序排列的。不知道为什么我有这个想法 - 可能是因为 in_addr 结构需要具有网络顺序的地址,inet_atoninet_addr 返回它们的值网络顺序,所以我的逻辑假设是这些常量可以按原样使用。快速拼凑出 5 行代码来检验这一理论,结果却告诉我事实并非如此。如果任何当权者碰巧看到这一点,我会建议明确指出这些值实际上是按主机顺序而不是网络顺序,并且 htonl() 应该应用于它们。为了保持一致性,我还建议,正如其他人已经在这里所做的那样,将 htonl() 用于所有 INADDR_* 值,即使它什么也不做到值。

Was going to add this as a comment, but it got a little long-winded ...

I think it's clear from the answers and the commentary here that htonl() needs to be used on these constants (albeit that calling it on INADDR_ANY and INADDR_NONE are tantamount to no-ops). The problem that I see as to where the confusion arises is that it is not explicitly called out in documentation - someone please correct me if I simply missed it, but I have not seen in the man pages, nor in the include header where it explicitly states that the defines for INADDR_* are in host order. Again, not a big deal for INADDR_ANY, INADDR_NONE, and INADDR_BROADCAST, but it is significant for INADDR_LOOPBACK.

Now, I've done quite a bit of low-level socket work in C, but the loopback address rarely, if ever, gets used in my code. Although this topic is over a year old, this very problem just jumped up to bite me in the behind today, and it was because I went on the mistaken assumption that the addresses defined in the include header are in network order. Not sure why I had that idea - probably because the in_addr structure needs to have the address in network order, inet_aton and inet_addr return their values in network order, and so my logical assumption was that these constants would be usable as-is. Throwing together a quick 5-liner to test that theory showed me otherwise. If any of the powers-that-be happen to see this, I would make the suggestion to explicitly call out that the values are, in fact, in host order, not network order, and that htonl() should be applied to them. For consistency's sake, I would also suggest, as others have done so already here, that htonl() be used for all of the INADDR_* values, even if it does nothing to the value.

七七 2024-11-16 01:11:16

让我们总结一下,因为之前的答案似乎都不是最新的,而且我可能不会是最后一个看到这个问题页面的人。对于在 INADDR_ANY 常量周围使用 htonl 或完全避免使用 htonl 存在意见

如今(现在已经有一段时间了)系统库大多支持 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,这只适用于我在函数内部的情况(而且我无法将其设为静态) code>),因为 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

这将是对标头的一个非常好的补充,然后您可以像使用 IPv6 一样轻松地使用 IPv4 常量。

但为了实现它,我必须使用一些常量来初始化它。当我确切地知道各个字节时,我不需要任何常量。正如有些人声称 htonl() 对于计算结果为零的常量来说是多余的,其他任何人都可以声称常量本身也是多余的。他是对的。

在代码中我更喜欢显式而不是隐式。因此,如果这些常量(如 INADDR_ANY、INADDR_ALL、INADDR_LOOPBACK)都一致地采用主机字节顺序,那么只有这样对待它们才是正确的。例如(当不使用上述常量时):

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

当然,您可以说您不需要为 INADDR_ANY 调用 htonl ,因此您可以:

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. < p>它是一个用于将任何主机名/服务/家族/socktype/协议转换为a的函数
    到匹配的struct addrinfo记录的列表。

  2. 每个struct addrinfo都包含一个指向struct sockaddr的多态指针,您可以直接与connect()一起使用它。因此,您不需要关心 struct sockaddr_in 的构造、到 struct sockaddr 的类型转换(通过指针)等。

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

    记录依次包含 connect() 调用所需的指针多态 struct sockaddr 结构。

因此,结论是:

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 为 null 且 hints.ai_family 设置时,一个好的实现会返回请求的环回地址。

Let's summarize it a little bit, as none of the previous answers seems to be up to date and I may not be the last person who will see this question page. There have been opinions both for and against usage of htonl around INADDR_ANY constant or avoiding it entirely.

Nowadays (and it's been nowadays for quite some time now) system libraries are mostly IPv6 ready, so we use IPv4 as well as IPv6. The situation with IPv6 is much easier as the data structures and constants don't suffer from byte order. One would use 'in6addr_any' as well as 'in6addr_loopback' (both struct in6_addr type) and both of them are constant objects in the network byte order.

See why IPv6 doesn't suffer from the same problem (if IPv4 addresses were defined as four byte arrays they wouldn't suffer either):

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

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

For IPv4, it would be nice to also have 'inaddr_any' and 'inaddr_loopback' as 'struct in_addr' constants (so that they can also be compared with memcmp or copied with memcpy). Indeed it might be a good idea to create them in your program as they aren't provided by glibc and other libraries:

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

With glibc, this only works for me inside a function (and I can't make it static), as htonl is not a macro but an ordinary function.

The problem is that glibc (in contrast with what was claimed in other answers) doesn't provide htonl as a macro but rather as a function. Therefore you would have to:

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

That would be a really nice addition to the headers and then you could work with IPv4 constants as easily as you can with IPv6.

But then to implement that, I had to use some constants to initialize that. When I know the respective bytes exactly, I don't need any constants. Just as some people claim that htonl() is redundant for a constant that evaluates to zero, anyone else could claim that the constant itself is redundant as well. And he would be right.

In the code I prefer to be explicit than implicit. Therefore if those constants (like INADDR_ANY, INADDR_ALL, INADDR_LOOPBACK) are all consistently in host byte order, then it's only correct if you treat them like that. See for example (when not using the above constant):

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

Of course you could say that you don't need to call htonl for INADDR_ANY and therefore you could:

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

But then when ignoring the byte order of the constant because it's zero anyway, then I don't see much logic in using the constant at all. And the same applies to INADDR_ALL, as it's easy to type 0xffffffff as well;

Another way to get around it is to avoid setting those values directly altogether:

struct in_addr address4;

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

This adds a little bit of useless processing but it has no byte order problems and it is virtually the same for IPv4 and IPv6 (you just change the address string).

But the question is why are you doing that at all. If you want to connect() to IPv4 localhost (but sometimes to IPv6 localhost, or just any hostname), getaddrinfo() (mentioned in one of the answers) is much better for that, as:

  1. It is a function used for translating any hostname/service/family/socktype/protocol a
    to a list of matching struct addrinfo records.

  2. Each struct addrinfo includes a polymorphic pointer to struct sockaddr that you can directly use with connect(). Therefore you don't need to care about the construction of struct sockaddr_in, typecasting (via a pointer) to struct sockaddr, etc.

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

    record that in turn include pointers polymorphic struct sockaddr structures which you need for the connect() call.

So, the conclusion is:

1) The standard API fails to provide directly usable struct in_addr constants (instead it provides rather useless unsigned integer constants in host order).

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);
}

When you are sure your query is selective enough that it only returns one result, you could do (omitting error handling for brevity) the following:

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);

If you're afraid getaddrinfo() might be significantly slower than using the constants, the system library is the best place to fix that. A good implementation would just return the requested loopback address when service is null and hints.ai_family is set.

强辩 2024-11-16 01:11:16

当已经有“体面”的答案时,我通常不喜欢回答。在这种情况下,我将破例,因为我添加到这些答案中的信息被误解了。

INADDR_ANY 定义为全零位 IPv4 地址、0.0.0.00x00000000。对此值调用 htonl() 将得到相同的值,即零。因此,在这个常量值上调用 htonl() 在技术上是没有必要的。

INADDR_ALL 定义为全一位 IPv4 地址,255.255.255.2550xFFFFFFFF。使用 INADDR_ALL 调用 htonl() 将返回 INADDR_ALL。同样,调用 htonl() 在技术上并不是必需的。

头文件中定义的另一个常量是 INADDR_LOOPBACK ,定义为 127.0.0.1 或 0x7F000001 。该地址以网络字节顺序给出,如果没有 htonl() 则无法传递到套接字接口。您必须将 htonl() 与此常量一起使用。

有些人建议,一致性和代码可读性要求程序员对任何名为 INADDR_* 的常量使用 htonl() ——因为其中一些常量需要它。这些海报是错误的。

该线程中给出的示例是:

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

引用“John Zwinck”:

“如果我正在审查这段代码,我会立即质疑为什么其中一个常量应用了 htonl 而另一个没有应用。我将其报告为错误,无论我是否碰巧知道 INADDR_ANY 始终是“内部知识” 0,所以转换它是一个无操作,我认为(并希望)许多其他维护者也会这样做。”

如果我收到这样的错误报告,我会立即扔掉它。这个过程会节省我很多时间,从那些不具备 INADDR_ANY 始终为 0 的“最低基本知识”的人那里获取错误报告。(建议了解 INADDR_ANY 的值 等人以某种方式违反了封装或其他不可行的内容 - 在 netcat 输出中和内核内部使用了相同的数字。 WHO不知道并不缺乏内部知识,他们缺乏基本该领域的知识。)

真的,如果你有一个程序员维护套接字代码,而那个程序员不这样做不知道 INADDR_ANY 和 INADDR_ALL 的位模式,你已经有麻烦了。将 0 包装在返回 0 的宏中是一种心态,它是无意义的一致性的奴隶,并且不尊重领域知识。

维护套接字代码不仅仅是了解 C。如果您在与 netstat 输出兼容的级别上不了解 INADDR_LOOPBACKINADDR_ANY 之间的区别,那么您的代码很危险,不应该更改它。

Zwinck 提出的关于不必要使用 htonl() 的稻草人论点:

  1. 使用 htonl 可能会冒犯经验丰富的套接字程序员,因为他们知道它不会执行任何操作(因为他们熟记常量的值) )。

这是一个稻草人的论点,因为我们有这样的描述:有经验的套接字程序员都牢记INADDR_ANY的值。这就像写道只有有经验的 C 程序员才能记住NULL 的值。 “背”字给人的感觉是这个数字有点难记,可能就是几个数字,比如127.0.0.1。但不,我们正在夸张地讨论记住“全零位”和“全一位”模式的难度。

考虑到这些数值出现在例如 netstat 和其他系统实用程序的输出中,并且还考虑到其中一些值出现在 IP 标头中,因此不存在称职的套接字程序员不知道这些价值观,无论是用心还是用脑。事实上,在不了解这些基础知识的情况下尝试套接字编程可能会对网络可用性造成危险。

  1. 省略它需要更少的输入。

这个论点本意是荒谬的、不屑一顾的,所以不需要太多反驳。

  1. 虚假的“性能”优化(显然这并不重要)。

很难知道这个论点从何而来。这可能是试图向反对派提供看似愚蠢的论点。无论如何,当您提供常量并使用典型的 C 编译器时,不使用 htonl() 宏对性能没有任何影响 - 无论哪种情况,常量表达式都会简化为常量。


不将 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_ANYhtonl() 没有“正确”的用法——两者是等效的。我不建议采用以任何特定方式使用该值的要求,因为 INADDR_X 常量系列只有四个成员,并且其中只有一个 INADDR_LOOPBACK 有一个值根据字节顺序而不同。最好只知道这一事实,而不是建立一个对值的位模式“视而不见”的使用值标准。

在许多其他 API 中,程序员在不知道 API 使用的常量的数值或位模式的情况下继续操作是很有价值的。对于套接字 API,这些位模式和值用作输入并普遍显示。最好以数字方式了解这些值,而不是花时间考虑对它们使用 htonl()

尤其是在用 C 语言编程时,套接字 API 的大多数“使用”都涉及获取其他人的源代码并对其进行调整。这是在接触使用 INADDR_ANY 的行之前了解它如此重要的另一个原因。

I don't usually like to answer when there is already a "decent" answer. In this case, I am going to make an exception because information I added to these answers is being misconstrued.

INADDR_ANY is defined as an all-zero-bits IPv4 address, 0.0.0.0 or 0x00000000. Calling htonl() on this value will result in the same value, zero. Therefore, calling htonl() on this constant value is not technically necessary.

INADDR_ALL is defined as an all-one-bits IPv4 address, 255.255.255.255 or 0xFFFFFFFF. Calling htonl() with INADDR_ALL will return INADDR_ALL. Again, calling htonl() is not technically necessary.

Another constant defined in the header files is INADDR_LOOPBACK, defined as 127.0.0.1, or 0x7F000001. This address is given in network-byte order, and cannot be passed to the sockets interface without htonl(). You must use htonl() with this constant.

Some would suggest that consistency and code readability demand that programmers use htonl() for any constant named INADDR_* -- because it is required for some of them. These posters are wrong.

An example given in this thread is:

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

Quoting from "John Zwinck":

"If I were reviewing this code, I would immediately question why one of the constants has htonl applied and the other does not. And I report it as a bug, whether or not I happened to have the "inside knowledge" that INADDR_ANY is always 0 so converting it is a no-op. And I think (and hope) many other maintainers would do the same."

If I were receiving such a bug report, I would immediately throw it away. This process would save me a lot of time, fielding bug reports from people who don't have the "basic minimum knowledge" that INADDR_ANY is always 0. (Suggesting that knowing the values of INADDR_ANY et al. somehow violates encapsulation or whatever is another non-starter -- the same numbers are used in the netcat output and inside the kernel. Programmers need to know the actual numerical values. People who don't know aren't lacking inside knowledge, they are lacking basic knowledge of the area.)

Really, if you have a programmer maintaining sockets code, and that programmer doesn't know the bit patterns of INADDR_ANY and INADDR_ALL, you are already in trouble. Wrapping 0 in a macro which returns 0 is the kind of mentality that is a slave to meaningless consistency and doesn't respect domain knowledge.

Maintaining sockets code is about more than understanding C. If you don't understand the difference between INADDR_LOOPBACK and INADDR_ANY at a level compatible with netstat output, then you are dangerous in that code and shouldn't be changing it.

Straw-man arguments proposed by Zwinck regarding the needless use of htonl():

  1. It may offend experienced socket programmers to use htonl because they will know it does nothing (since they know the value of the constant by heart).

This is a straw argument because we have a portrayal that experienced socket programmers know the value of INADDR_ANY by heart. This is like writing that only an experienced C programmer knows the value of NULL by heart. Writing "by heart" gives the impression that the number is slight difficult to memorize, perhaps a few digits, such as 127.0.0.1. But no, we are hyperbolically discussing the difficult of memorizing the patterns named "all zero bits" and "all one bits."

Considering that these numerical values appear in the output of, e.g., netstat and other system utilities, and also considering that some of these values appear in IP headers, there is no such thing as a competent sockets programmer who does not know these values, whether by heart or by brain. In fact, attempting sockets programming without knowing these basics can be dangerous to the network availability.

  1. It requires less typing to omit it.

This argument is intended to be absurd and dismissive, so it doesn't need much refuting.

  1. A bogus "performance" optimization (clearly it won't matter).

It's hard to know where this argument came from. It could be an attempt to supply stupid-seeming arguments to the opposition. In any case, not using the htonl() macro makes no difference to performance when you provide a constant and use a typical C compiler -- the constant expressions are reduced to a constant in either case.


A reason not to use htonl() with INADDR_ANY is that most experienced sockets programmer knows that it is not needed. What's more: those programmers who do not know need to learn. There is no extra "cost" with use of htonl(), the trouble is the cost of establishing a coding standard which fosters ignorance of such critically important values.

By definition, encapsulation fosters ignorance. That very ignorance is the usual benefit of using an encapsulated interface -- knowledge is expensive and finite, therefore encapsulation is usually good. The question becomes: which efforts of programming are best enhanced via encapsulation? Are there programming tasks which are disserved by encapsulation?

It is not technically incorrect to use htonl(), because it has no effect on this value. However, arguments that you should use it may be misleading.

There are those who would argue that a better situation would be one in which the developer did not need to know that INADDR_ANY is all zeroes and so on. This land of ignorance is worse, not better. Consider that these "magic values" are used throughout various interfaces with TCP/IP. For example, when configuring Apache, if you would like to listen only to IPv4 (and not IPv6), you must specify:

Listen 0.0.0.0:80

I have run into programmers who mistakenly supplied the local IP address instead of INADDR_ANY (0.0.0.0) above. These programmers don't know what INADDR_ANY is, and they probably wrap it in htonl() while they are at it. This is the land of abstaction-thinking and encapsulating.

The ideas of "encapsulation" and "abstraction" have been widely accepted and too-widely applied, but they do not always apply. In the domain of IPv4 addressing, it's not appropriate to treat these constant values as "abstract" -- they are converted directly into bits on the wire.


My point is this: there is no "correct" usage of INADDR_ANY with htonl() -- both are equivalent. I would not recommend adopting a requirement that the value be used any particular way, because the INADDR_X family of constants only have four members, and only one of them, INADDR_LOOPBACK has a value which is different depending on byte ordering. It is better to just know this fact than to establish a standard for using the values which turns a "blind eye" to the bit patterns of the values.

In many other APIs, it is valuable for programmers to proceed without knowing the numeric value or bit patterns of constants used by the APIs. In the case of the sockets API, these bit patterns and values are used as input and displayed pervasively. It is better to know these values numerically than to spend time thinking about using htonl() on them.

When programming in C, especially, most "use" of the sockets API involves grabbing some other person's source code, and adapting it. This is another reason it is so important to know what INADDR_ANY is before touching a line which uses it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文