我遇到了两个主题:
Socket with recv-timeout: What is wrong with this code?
Reading / Writing to a socket using a FILE stream in c
一个使用htonl
而另一个不使用。
哪个是对的?
答案 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 :(得分:16)
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
。
答案 2 :(得分:8)
正确,从某种意义上说,INADDR_ANY
和htonl
都已弃用,导致只能与IPv4一起使用的复杂,丑陋的代码。切换到使用getaddrinfo
来满足您的所有套接字地址创建需求:
struct addrinfo *ai, hints = { .ai_flags = AI_PASSIVE|AI_ADDRCONFIG };
getaddrinfo(0, "1234", &hints, &ai);
将"1234"
替换为您的端口号或服务名称。
答案 3 :(得分:3)
是否会将此作为评论添加,但它有点啰嗦......
我认为从答案和评论中可以清楚地看出htonl()
需要在这些常量上使用{尽管在INADDR_ANY
和INADDR_NONE
上调用它等同于无操作)。我在混淆出现的地方看到的问题是它没有在文档中明确地说出来 - 有人请你纠正我,如果我只是错过它,但是我没有在手册页中看到过,也没有在包含头文件中明确地看到它说明INADDR_*
的定义是主机顺序。同样,对于INADDR_ANY
,INADDR_NONE
和INADDR_BROADCAST
来说,这不是什么大问题,但 对INADDR_LOOPBACK
来说很重要。
现在,我已经在C中完成了相当多的低级套接字工作,但很少(如果有的话)在我的代码中使用了环回地址。虽然这个主题已经有一年多的时间了,但是这个问题在今天刚刚开始让我陷入困境,这是因为我错误地假设include头中定义的地址是按网络顺序排列的。不知道为什么我有这个想法 - 可能因为in_addr
结构需要按网络顺序排列地址,inet_aton
和inet_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_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
,我们设置了 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()(在其中一个答案中提到)要好得多,如:
它是用于翻译任何主机名/服务/系列/ socktype / protocol的函数
到匹配的struct addrinfo
条记录列表。
每个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()
结构。
所以,结论是:
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)