您的位置:首页 > 其它

htonl() htons() ntohl() ntohs() inet_ntoa() inet_addr()的用法

2016-12-02 12:01 816 查看
inet_addr函数需要一个字符串作为其参数,该字符串指定了以点分十进制格式表示的IP地址(例如:192.168.0.16),而且inet_addr函数会返回一个适合分配给S_addr的u_long类型的数值。

       eg:ina.sin_addr.s_addr = inet_addr("132.241.5.10");

inet_ntoa函数会完成相反的转换,它接受一个in_addr结构体类型的参数,并返回一个以点分十进制格式表示的IP地址字符串。

       eg:printf("%s",inet_ntoa(ina.sin_addr));

htonl()表示将32位的主机字节顺序转化为32位的网络字节顺序(host to network long)。
htons()表示将16位的主机字节顺序转化为16位的网络字节顺序(host to network short)。
ntohl()表示将32位的网络字节顺序转化为32位的主机字节顺序(network to host long)。
ntohs()表示将16位的网络字节顺序转化为16位的主机字节顺序(network to host short)。

 

在详细分析各函数之前,我们先来看个相关知识点:

在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题,这正是因为计算机数据表示存在两种字节顺序:NBO与HBO。

网络字节顺序NBO(Network ByteOrder):网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式,按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。
主机字节顺序(HBO,Host ByteOrder):不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机字节序。最常见的有两种(1)Little endian(小端模式)最符合人的思维的字节序:低字节存高地址,高字节存低地址。怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说低位值最小,就应该放在内存地址小的地方,也即内存地址地位;反之,高位值就应该放在内存地址大的地方,也即内存地址高位。(2)Big endian(大端模式)最直观的字节序:低字节存低地址,高字节存高地址。为什么说直观,因为这种方式不考虑对应关系,只需要把内存地址从左到右按照由低到高的顺序写出,把值按照通常的高位到低位的顺序写出,两者对照,一个字节一个字节的填充进去。

eg:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为:

内存地址

Little endian

Big endian

0x0000

0xcd

0x12

0x0001

0xab

0x34

0x0002

0x34

0xab

0x0002

0x12

0xcd

注:x86系列CPU都是Littleendian的字节序。

不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。如 Intel x86结构下,short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12。再如IBM power PC结构下,short型数0x1234表示为12 34,int型数0x12345678表示为12 34 56 78。  

由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同power pc那样的顺序。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换。 

 

1.  inet_addr()

函数原型:unsigned long PASCAL FAR inet_addr( const struct FAR* cp);

函数功能:将一个点间隔地址转换成一个in_addr。

一个以Internet标准“.”间隔的字符串,例如202.38.214.xx。当IP地址为255.255.255.255是被认为无效IP地址。本函数解释cp参数中的字符串,这个字符串用Internet的“.”间隔格式表示一个数字的Internet地址。返回值是一个无符号长整形数,可用作Internet地址,所有Internet地址以网络字节顺序返回(字节从左到右排列)。当

inet_addr()发生错误时返回-1。

注:当IP地址为255.255.255.255时,inet_addr()认为它是无效IP地址,而inet_ntoa()函数将其视为有效的IP。

2.  inet_ntoa()

函数原型:char FAR* PASCAL FAR inet_ntoa( struct in_addr in);

函数功能:将网络地址转换成“.”点隔的字符串格式。

("ntoa"的含义是"network to ascii")

本函数将一个用in参数所表示的Internet地址结构转换成以“.”间隔的诸如“a.b.c.d”的字符串形式。请注意inet_ntoa()返回的字符串存放在WINDOWS套接口实现所分配的内存中。应用程序不应假设该内存是如何分配的。在同一个线程的下一个WINDOWS套接口调用前,数据将保证是有效。若无错误发生,inet_ntoa()返回一个字符指针;否则的话,返回NULL。其中的数据应在下一个WINDOWS套接口调用前复制出来。

注:inet_ntoa()将结构体in-addr作为一个参数,不是长整形。还需要指出,该函数返回的是一个指向一个字符的指针,它是一个由inet_ntoa()控制的静态的固定的指针,所以每次调用 inet_ntoa(),它就将覆盖上次调用时所得的IP地址。

补充:还有个函数inet_aton(),与inet_ntoa()作用相反。

3.  htons()

函数原型:u_short PASCAL FAR htons( u_short hostshort);

函数功能:把unsigned short类型从主机序转换到网络序。

注:简单地说,htons()就是将一个数的高低位互换,如:12 34 --> 34 12。

eg:在Intel机器下,执行以下程序

int main()

{

<
4000
span style="font-size:18px;">           printf(“%d\n”,htons(16));

           return 0;

}

得到的结果是4096,解释如下:数字16的16进制表示为0x0010,数字4096的16进制表示为0x1000。由于Intel机器是小尾端,存储数字16时的实际顺序为1000,存储4096时的实际顺序为0010。因此在发送网络包时,为了报文中数据位0010,需要经过htons进行字节转换。如果用IBM等大尾机器,则没有这种字节顺序转换,但为了程序的可移植性,也最好用这个函数。

注:数字所占位数小于或等于一个字节(8 bits)时,不要用htons转换,这是因为对于主机来说,大小尾端的最小单位为字节(byte)。

4.  htonl()

函数原型:u_long PASCALFAR htonl( u_long hostlong);

函数功能:把unsigned long类型从主机序转换到网络序。

5.  ntohl()

函数原型:u_long PASCAL FAR ntohl( u_long netlong);

函数功能:把unsigned long类型从网络序转换到主机序。

6.  ntohs()

函数原型:u_short PASCAL FAR ntohs( u_short netshort);

函数功能:把unsigned short类型从网络序转换到主机序。

注:在使用Little endian的系统中,上面4个函数会把字节序进行转换;在使用Big endian类型的系统中,这些函数会定义成空宏。

 

测试代码:

include <stdio.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <string.h>

int main(int aargc, char* argv[])

{

        struct in_addr addr1,addr2;

        ulong   l1,l2;

        l1= inet_addr("192.168.0.74");

        l2 = inet_addr("211.100.21.179");

        memcpy(&addr1, &l1, 4);

         memcpy(&addr2, &l2, 4);

        printf("%s : %s\n", inet_ntoa(addr1), inet_ntoa(addr2));    //注意这一句的运行结果

        printf("%s\n", inet_ntoa(addr1));

        printf("%s\n", inet_ntoa(addr2));

         return 0;

}

实际运行结果如下:

192.168.0.74 : 192.168.0.74       //从这里可以看出,printf里的inet_ntoa只运行了一次。

192.168.0.74

211.100.21.179

这就解释了刚刚提出来的:inet_ntoa返回一个char *,而这个char *的空间是在inet_ntoa里面静态分配的,所以inet_ntoa后面的调用会覆盖上一次的调用。第一句printf的结果只能说明在printf里面的可变参数的求值是从右到左的,仅此而已。

 

补充:sockaddr_in , sockaddr , in_addr区别

struct  sockaddr  { 

                unsigned   short   sa_family;    

                char   sa_data[14];    

      }; 

  上面是通用的socket地址,具体到Internetsocket,用下面的结构,二者可以进行类型转换 

 struct   sockaddr_in   { 

                short   int  sin_family;    

                unsigned   short  int   sin_port;    

                struct   in_addr  sin_addr;    

                unsigned   char  sin_zero[8];    

       }; 

       struct   in_addr就是32位IP地址。 

       struct   in_addr  { 

                union {

                        struct { u_chars_b1,s_b2,s_b3,s_b4; } S_un_b;

                        struct { u_shorts_w1,s_w2; } S_un_w;

                        u_long S_addr;

                } S_un;

                #define s_addr  S_un.S_addr

                }; 

inet_addr()是将一个点分制的IP地址(如192.168.0.1)转换为上述结构中需要的32位IP地址(0xC0A80001)。填值的时候使用sockaddr_in结构,而作为函数(如socket,listen, bind等)的参数传入的时候转换成sockaddr结构就行了,毕竟都是16个字符长。

通常的用法是: 

 int   sockfd; 

 struct   sockaddr_in   my_addr; 

 sockfd   =   socket(AF_INET,   SOCK_STREAM,   0);    

 my_addr.sin_family   =   AF_INET;    

 my_addr.sin_port   =   htons(MYPORT);    

 my_addr.sin_addr.s_addr   =   inet_addr("192.168.0.1"); 

 bzero(&(my_addr.sin_zero),  8);    

 bind(sockfd,   (struct   sockaddr  *)&my_addr,  sizeof(struct   sockaddr));

可以用C++做个不太准确的假设。 

sockaddr是base class;sockaddr_in等是derived class 

如此一来,bind,connect,sendto,recvfrom等函数就可以使用base class来处理多种不同的derived class了。但是实际上,这是没有继承关系数据结构(C嘛),所以需要强制造型来转换数据类型。正因为如此,在sendto的时候需要给出len长度,因为不同的sockaddr_xx实现长度并不相同。

 

 

参考来源:
http://blog.csdn.net/cpp_funs/article/details/6988154 http://www.360doc.com/content/14/1113/16/16170632_424843985.shtml
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: