理解htonl()和ntohl()函数

24
我正在尝试使用Unix套接字向本地主机发送一些UDP数据包。据我了解,当设置IP地址和端口以便发送数据包时,我将使用转换为网络字节顺序的值填充我的sockaddr_in。我在使用OSX,并且对此感到惊讶。
printf("ntohl: %d\n", ntohl(4711));
printf("htonl: %d\n", htonl(4711));
printf("plain: %d\n", 4711);

打印

ntohl: 1729232896
htonl: 1729232896
plain: 4711

因此,这两个函数都不会返回原始值。据我所知,由于x86是小端字节序,我本来期望看到结果不同,或者完全相同且与实际数字4711相同。显然,我不了解htonlntohl及其变体的作用。我缺少什么?

相关代码如下:

int main(int argc, char *argv[])
{
   if (argc != 4)
   {
      fprintf(stderr, "%s\n", HELP);
      exit(-1);
   }

   in_addr_t rec_addr = inet_addr(argv[1]); // first arg is '127.0.0.1'
   in_port_t rec_port = atoi(argv[2]);      // second arg is port number
   printf("Address is %s\nPort is %d\n", argv[1], rec_port);
   char* inpath = argv[3];

   char* file_buf;
   unsigned long file_size = readFile(inpath, &file_buf); // I am trying to send a file
   if (file_size > 0)
   {
      struct sockaddr_in dest;
      dest.sin_family      = AF_INET;
      dest.sin_addr.s_addr = rec_addr; // here I would use htons
      dest.sin_port        = rec_port;
      printf("ntohs: %d\n", ntohl(4711));
      printf("htons: %d\n", htonl(4711));
      printf("plain: %d\n", 4711);
      int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
      if (socket_fd != -1)
      {
         int error;
         error = sendto(socket_fd, file_buf, file_size + 1, 0, (struct sockaddr*)&dest, sizeof(dest));
         if (error == -1)
            fprintf(stderr, "%s\n", strerror(errno));
         else printf("Sent %d bytes.\n", error);
      }
   }

   free(file_buf);
   return 0;
}

请注意,您的文本中提到了“htons”和“ntohs”,但实际上您正在调用htonl()ntohl() - John Bollinger
@JohnBollinger 是的,这是尝试了两者并得到相同结果后得出的结论,谢谢提醒。 - oarfish
4个回答

32
正如其他人提到的那样,无论是在小端机器上使用`htons`还是`ntohs`都会颠倒字节顺序,在大端机器上则不起作用。这些函数接收16位值并返回16位值。如果要转换32位值,则应改用`htonl`和`ntohl`。
这些函数的名称来自某些数据类型的传统大小。`s`代表`short`(短整型),而`l`代表`long`(长整型)。短整型通常为16位,而在旧系统中长整型为32位。
在您的代码中,您不需要对`rec_addr`调用`htonl`,因为该值是由`inet_addr`返回的,并且该函数以网络字节顺序返回地址。但是,您需要对`rec_port`调用`htons`。

如果我尝试在本地主机上运行的两个程序之间发送数据包,并使用htons将“rec_port”处理,那么8080就会变成36895。 - oarfish

14

"网络字节序"始终表示大端序。

"主机字节序"取决于主机的架构。根据CPU,主机字节序可能是小端序、大端序或其他类型。(g)libc 会适应主机架构。

由于Intel架构是小端序,这意味着这两个函数都在做相同的事情:颠倒字节顺序。


3
“网络字节序”逐渐演变为指大端序。但它并不总是这样,现在非常普遍。详见:https://en.wikipedia.org/wiki/Endianness#Networking。 - chux - Reinstate Monica

12

这两个函数都会反转字节顺序(在小端机器上)。为什么这会返回参数本身呢?

请尝试htons(ntohs(4711))ntohs(htons(4711))


1
嗯...好吧。我想真正的问题是为什么我无法向本地主机发送任何内容,除非我使用纯IP地址,但这是一个单独的问题。 - oarfish
4
如果在您的系统上需要反转字节,则需要“reverse the bytes”(反转字节)... 您可以猜测两者都需要反转字节或两者都不需要反转字节... 这取决于您的体系结构。 - Sandburg

10
这些函数的命名不太恰当。"主机到网络"和"网络到主机"实际上是相同的东西,应该被称为“如果这是小端机器,则改变字节序”。因此,在小端机器上,您需要执行以下操作:
net, ie be, number = htonl / ntohl (le number)

并将数字发送到线上。当您从线上获取大端数字时

le num = htonl/ntohl (net ,ie be, number)

在大型端设备上
net, ie be, number = htonl / ntohl (be number)

并且

 be num = htonl/ntohl (net ,ie be, number)

在最后的情况下,你会发现这些函数什么也不做。

1
要小心。虽然现在很少有使用字节顺序不同于小端和大端的机器,但过去确实有这样的机器,而且一些机器曾经很显著。可以想象,现在或将来可能存在这样的机器,例如htonl()不是它自己的逆运算。 POSIX设计了这些函数以便能够处理这个问题,以防万一需要处理。 - John Bollinger
好的,那么它应该被称为“如果这不是一个大端机器,则改变字节序为大端”。 - pm100
11
即使两个函数执行相同的功能,它们拥有不同的名称仍然很有用,因为它能够澄清代码的意图。例如,如果我看到 x=ntohl(blah),我知道赋值给 x 的值具有语义上的意义;相反,如果我看到 x=htonl(blah),我知道 x 的值不能被依赖为本机字节序。如果代码只是 x=maybe_endian_swap(blah),那么我不知道是否可以在之后使用 printf("%i\n", x); 打印出一个具有语义意义的值。 - Jeremy Friesner
4
字节序不是关于起点的观点,而是关于目标地点的观点。来源:https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html - Sandburg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接