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

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

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

我的问题是我试图打印由htonl返回的数据的值。 这是我的例子:

 #include  #include  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天。 看似简单的事情让我彻底失望了。 我不希望任何人解决问题,但推动正确的方向将是非常有帮助的。

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

uint32_t htonl(uint32_t hostlong);

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

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

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

  #include  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,l代表long。 术语“短”和“长”是来自4.2BSD的Digital VAX实现的历史文物。 我们应该将s视为16位值(例如TCP或UDP端口号),将l视为32位值(例如IPv4地址)。 实际上,在64位数字Alpha上,长整数占用64位,而htonl和ntohl函数在32位值上运行。 使用这些函数时,我们不关心主机字节顺序和网络字节顺序的实际值(big-endian或littleendian)。 我们必须做的是调用适当的函数来在主机和网络字节顺序之间转换给定值。 在那些与Internet协议(big-endian)具有相同字节顺序的系统上,这四个函数通常被定义为空宏。 我们将更多地讨论字节排序问题,关于网络数据包中包含的数据而不是协议头中的字段,我们还没有定义术语“字节”。 我们使用该术语表示8位数量,因为几乎所有当前的计算机系统都使用8位字节。 大多数Internet标准使用术语八位字节而不是字节来表示8位数量。 这开始于TCP / IP的早期阶段,因为早期的大部分工作都是在诸如DEC-10之类的系统上完成的,这些系统没有使用8位字节。 Internet标准中的另一个重要约定是位排序。 在许多Internet标准中,您将看到与以下内容类似的数据包“图片”(这是RFC 791中IPv4头的前32位):

 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)时不起作用。

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

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

考虑一下你的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 

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

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

 uint32_t address_count = ~ntohl(netmask) + 1; 

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