与网络字节顺序和主机字节顺序混淆

时间:2015-08-25 13:37:20

标签: c

我对主机字节顺序网络字节顺序感到非常困惑。我知道网络字节顺序是大端。我知道在我的情况下主机字节顺序是小端。

那么,如果我打印数据,我需要转换为主机字节顺序才能获得正确的值吗?

我的问题是我正在尝试打印htonl返回的数据值。这是我的例子:

#include <stdio.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{   
    int bits = 12;
    char *ip = "132.89.39.0";
    struct in_addr addr;
    uint32_t network, netmask, last_addr;
    uint32_t total_hosts;

    inet_aton(ip, &addr);
    printf("Starting IP:\t%s\n", inet_ntoa(addr.s_addr));

    netmask = (0xFFFFFFFFUL << (32 - bits)) & 0xFFFFFFFFUL;
    netmask = htonl(netmask);
    printf("Netmask:\t%s\n", inet_ntoa(netmask));

    network = addr.s_addr & netmask;
    printf("Network:\t%s\n", inet_ntoa(network));

    printf("Total Hosts:\t%d\n", ntohl(netmask));

    return 0;

}

printf("Total Hosts:\t%d\n", ntohl(netmask));打印正确的值,但打印时带有减号。如果我使用%u,我的值就会错误。

我哪里错了?

%d输出为:

Starting IP:    132.89.39.0
Netmask:        255.240.0.0
Network:        132.80.0.0
Total Hosts:    -1048576

%u输出为:

Starting IP:    132.89.39.0
Netmask:        255.240.0.0
Network:        132.80.0.0
Total Hosts:    4293918720

我已经坚持了2天。看似简单的事情让我彻底失望了。我不希望任何人解决问题,但是朝着正确的方向努力会非常有帮助。

3 个答案:

答案 0 :(得分:5)

如果你看到,htonl()的原型是

  

uint32_t htonl(uint32_t hostlong);

因此,它返回一个uint32_t,它是无符号类型。使用%d打印该值(期望类型为signed int的参数)是不合适的。

至少,您需要使用%u来获取unsigned值。通常,如果可能,请尝试使用PRIu32 MACRO打印固定宽度(32)无符号整数。

答案 1 :(得分:2)

目前有许多系统可以在little-endian和bigendian之间进行更改 字节排序,有时在系统重置时,有时在运行时。 我们必须将这些字节排序差异作为网络程序员处理,因为 网络协议必须指定网络字节顺序。例如,在TCP段中 是一个16位端口号和一个32位IPv4地址。发送协议栈和 接收协议栈必须就这些多字节字段的字节顺序达成一致 将被传送。 Internet协议对这些多字节使用大端字节排序 整数。 理论上,实现可以将字段存储在主机字节中的套接字地址结构中 在将字段移入和移出字段时,顺序然后转换为网络字节顺序 协议标题,使我们不必担心这个细节。但是,历史和 POSIX规范说套接字地址结构中的某些字段必须是 以网络字节顺序维护。因此,我们关心的是在主机字节顺序之间进 和网络字节顺序。我们使用以下四个函数在这两个字节之间进行转换 订单。

 #include <netinet/in.h>
    uint16_t htons(uint16_t host16bitvalue) ;
    uint32_t htonl(uint32_t host32bitvalue) ;

返回:网络字节顺序中的值

   uint16_t ntohs(uint16_t net16bitvalue) ;
    uint32_t ntohl(uint32_t net32bitvalue) ;

返回:主机字节顺序中的值

在这些函数的名称中,h代表host,n代表network,s代表short, 我代表很久。术语&#34;短&#34;和&#34;长&#34;是Digital VAX的历史文物 4.2BSD的实施。我们应该将s视为16位值(例如TCP或 UDP端口号)和l作为32位值(例如IPv4地址)。的确,在64位上 数字Alpha,一个长整数占用64位,但是htonl和ntohl函数可以运行 32位值。 使用这些函数时,我们不关心实际值(big-endian或littleendian) 用于主机字节顺序和网络字节顺序。我们必须做的是打电话给 适当的函数,用于在主机和网络字节顺序之间转换给定值。上 这些系统具有与Internet协议(big-endian)相同的字节顺序 四个函数通常被定义为空宏。 关于a中包含的数据,我们将更多地讨论字节排序问题 网络数据包而不是协议头中的字段, 我们还没有定义术语&#34;字节。&#34;我们使用该术语表示8位数量 几乎所有当前的计算机系统都使用8位字节。大多数互联网标准使用该术语 八位字节而不是字节表示8位数量。这开始于TCP / IP的早期阶段 因为早期的大部分工作都是在DEC-10这样没有使用的系统上完成的 8位字节。 Internet标准中的另一个重要约定是位排序。在很多互联网上 标准,你会看到&#34;图片&#34;看起来类似于以下的数据包(这是第一个 来自RFC 791的32位IPv4标头):

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL|   TYPE OF SERCVICE |      TOTAL LENGTH          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 

这表示它们在线路上出现的顺序为四个字节;最左边的位是 最重要的。但是,编号从分配给最高有效位的零开始。 这是一种您应该熟悉的符号,以便更容易阅读协议 RFC中的定义。 20世纪80年代常见的网络编程错误是在Sun上开发代码 工作站(大端摩托罗拉68000s)并忘记调用这四个功能中的任何一个。 代码在这些工作站上运行良好,但在移植到littleendian时不起作用 机器(如VAXes)。

答案 2 :(得分:1)

此处的问题不是您在网络和主机订单之间的转换。你的代码部分完美无缺。

问题在于您认为网络掩码(解释为整数)是与该掩码匹配的主机数。这正是事实的反面。

考虑您的12位网络掩码,255.240.0.0。或者,二进制:

1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

如您的代码所示,对于与此网络掩码匹配网络地址的主机地址,两个地址必须相同,其中网络掩码具有1位。可以自由选择与网络掩码中的0对应的位位置。可以通过仅考虑0位来确定这样的地址的数量。但当然我们不能将这些位留为0;要计算符合条件的地址数量,我们需要预先设置1。因此,在这种情况下,计数(完全如您所怀)1,048,576:

                      1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

计算此值的一种方法是反转网络掩码并添加1:

1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
---------------------------------------------------------------
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 (bitwise invert)
                                                              1 (+ 1)
---------------------------------------------------------------
                      1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

在二进制补码算法中,这与算术否定完全相同。因此,当您将网络掩码打印为有符号整数时,您会看到预期计数的负数并不令人惊讶。 (将未签名的uint32_t打印为带符号的int在技术上是未定义的行为,但可能会在具有32位整数的二进制补码机器上按预期工作。)

简而言之,您应该如何计算网络掩码中合格地址的数量:

uint32_t address_count = ~ntohl(netmask) + 1;

(大多数编译器将优化为一元否定操作码,如果可用的话。)