sin_addr.s_addr = INADDR_ANY;需要什么?

时间:2011-05-21 12:58:41

标签: c sockets

我遇到了两个主题:

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

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

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

哪个是对的?

7 个答案:

答案 0 :(得分:33)

由于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。而且我会将其报告为一个错误,无论我是否碰巧拥有htonl始终为0的“内部知识”,因此将其转换为无操作。

您编写的代码不仅仅是关于具有正确的运行时行为,它应该在可能的情况下显而易见并且容易相信它是正确的。因此,您不应该删除INADDR_ANY周围的htonl。我可以看到不使用htonl的三个原因是:

  1. 它可能冒犯有经验的套接字程序员使用{{1}},因为他们知道它什么也不做(因为他们知道常量的值)。
  2. 省略它需要更少的输入。
  3. 虚假的“表现”优化(显然无关紧要)。

答案 1 :(得分: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_LOOPBACKhtonl是必需的。

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

答案 2 :(得分:8)

正确,从某种意义上说,INADDR_ANYhtonl都已弃用,导致只能与IPv4一起使用的复杂,丑陋的代码。切换到使用getaddrinfo来满足您的所有套接字地址创建需求:

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

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

答案 3 :(得分:3)

是否会将此作为评论添加,但它有点啰嗦......

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

现在,我已经在C中完成了相当多的低级套接字工作,但很少(如果有的话)在我的代码中使用了环回地址。虽然这个主题已经有一年多的时间了,但是这个问题在今天刚刚开始让我陷入困境,这是因为我错误地假设include头中定义的地址是按网络顺序排列的。不知道为什么我有这个想法 - 可能因为in_addr结构需要按网络顺序排列地址,inet_atoninet_addr按网络顺序返回它们的值,所以我的逻辑假设是这些常量可以按原样使用。把一个快速的5-liner扔在一起来测试那个理论告诉我的情况。如果有任何权力碰巧看到这一点,我会建议明确地说出这些值实际上是按主机顺序而不是网络顺序,htonl()应该应用于他们。为了保持一致性,我还建议,正如其他人已在此处所做的那样,htonl()用于所有INADDR_*值,即使它对值没有任何作用。

答案 4 :(得分:2)

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

当前版本的FreeBSD在INADDR_中定义了12个netinet/in.h常量; 12个中的9个需要htonl()才能获得正确的功能。 (9个是INADDR_LOOPBACK和其他8个多播组地址,例如INADDR_ALLHOSTS_GROUPINADDR_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,我们设置了   Internet地址INADDR_ANY和。{   将16位Internet端口归零。

答案 5 :(得分:2)

让我们总结一下,因为以前的答案似乎都不是最新的,我可能不是最后一个会看到这个问题页面的人。对于在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,这只适用于函数内部(我不能使它成为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

这对标题来说是一个非常好的补充,然后你可以像使用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 localhost(但有时候是IPv6 localhost,或者只是任何主机名),getaddrinfo()(在其中一个答案中提到)要好得多,如:

  1. 它是用于翻译任何主机名/服务/系列/ socktype / protocol的函数 到匹配的struct addrinfo条记录列表。

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

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

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

  3. 所以,结论是:

    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时,一个好的实现只会返回请求的环回地址。

答案 6 :(得分:0)