ip(点分十进制 <==> 二进制整数)之间的转换

linux的套接字部分比较容易混乱,在这里稍微总结一下。

地址转换函数在地址的文本表达式它们存放在套接字地址结构中的二进制值进行转换。

地址转换函数有四个:其中inet_addr 和 inet_ntoa适用于IPv4,inet_pton 和 inet_ntop同时适于用IPv4和IPv6。

  • 套接字地址结构分为IPv4套接字地址结构sockaddr_in和IPv6套接字地址结构sockaddr_in6。其中IPv4的套接字地址结构如下。
    • IPv4套接字地址结构:(定义在<netinet/in.h>头文件中)
       1 struct in_addr{
       2     in_addr_t s_addr;    //
       3 };
       4 
       5 struct sockaddr_in{
       6     uint8_t          sin_len;
       7     sa_family_t      sin_family;    //套接字地址结构的地址族
       8     in_port_t        sin_port       //TCP或UDP端口,一般为uint16_t
       9     struct in_addr  sin_addr;       //IPv4地址,一般为uint32_t
      10     char             sin_zero[8];
      11 };
      • 说明:POSIX规范只需要这个结构中的3个字段:sin_family、sin_addr和sin_port。对于符合POSIX的实现来说,定义额外的结构字段是可以接受的。几乎所有的实现都增加了sin_zero字段,所以所有的套接字地址结构大小都至少是16字节。

      • IPv4地址和TCP或UDP端口号在套接字地址结构中总是以网络字节序来存储,在使用这些字段时,必须要牢记这一点。
      • 32位IPv4地址存在两种不同的访问方法。例如:如果serv第一位某个网际套接字地址结构,那么serv.sin_addr将按in_addr结构引用其中的32位IPv4地址,而serv.sin_addr.s_addr将按in_addr_t(通常是一个uint32_t)引用同一个32位Ipv4地址。在将它作为函数的参数时要注意使用正确的IPv4地址,因为编译器对传递结构和传递正数的处理是完全不同的。
      • sin_zero字段未曾使用,不过在填写这种套接字地址结构时,总是把该字段置为0(总是在填写前把整个结构置为0)
      • 套接字地址结构仅在给定主机上使用:虽然结构中的某些字段(例如IP地址和端口号)用在不同主机之间的通信,但是结构本身并不在主机之间传递。
  • 通用套接字地址结构:
    • 当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递。然而以这样的指针作为参数之一的任何套接字函数必须处理来自所有支持的任何协议族的套接字地址结构。在如何声明所传递指针的数据类型上存在一个问题,有了ANSI C后解决办法:void *。然而在ANSI C之前的解决办法是在<sys/socket.h>中定义一个通用的套接字地址结构:
      1 struct sockaddr{
      2     uint8_t      sa_len;
      3     sa_family_t  sa_family;
      4     char         sa_data[14];
      5 };

      于是套接字函数被定义为以指向某个通用套接字地址结构的一个指针作为其参数之一,例如bind函数的ANSI C函数原型:

      int bind(int, struct sockaddr *, socklen_t);    //一般是uint32_t

      这就要求对这些函数的任何调用都必须要将指向特定于协议的套接字地址结构的指针进行强制类型转换,变成指向某个通用套接字地址类型的指针,如:

      struct sockaddr_in serv;
      
      bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
  • 值--结果参数
    • 当向一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递,也就是说传递的是一个指向该结构的一个指针。该结构的长度也作为一个参数来传递,但是其传递方式取决于该结构的传递方向:进程-->内核,内核-->进程
      • 从进程到内核传递套接字地址结构的函数有3个:bind,connect,sendto。这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小,例如:
        struct sockaddr_in serv;
        
        connect(sockdf, (SA *) &serv, sizof(serv));
      • 从内核到进程传递套接字地址结构的函数有4个:accept, recvfrom,getsockname和getpeername。这4个函数的其中两个参数是指向某个套接字地址结构的指针和指向表示该结构大小的整数的指针,如:
        struct sockaddr_un cli;
        socklen_t len;
        
        len = sizeof(cli);
        getpeername(unixfd, (SA *) &cli, &len);
    • 把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当单数被调用时,结构大小是一个值,它告诉内核结构的大小,这样内核在写该结构时不至于越界;当函数返回时,结构大小又是一个结果,它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数成为值-结果参数。
  • 网络字节序 <--> 主机字节序:有四个函数用于网络字节序和主机字节序之间的转换:
    • 以下两个均返回网络字节序
    • uint16_t htons(uint16_t host16bitvalue);
    • uint32_t htonl(uint32_t host32bitvalue);
    • 以下两个均返回主机字节序
    • uint16_t ntohs(uint16_t net16bitvalue);
    • uint32_t ntohs(uint32_t net32bitvalue);
  • 地址转换函数(两组):
    • inet_aton, inet_addr, inet_ntoa:这一组函数在点分十进制字符串("206.168.112.96")与它长度为32位的网络字节序二进制值间转换IPv4地址。原型如下:
      #include <arpa/inet.h>
      
      int inet_aton(const char *strptr, struct in_addr *addrptr);    //返回:若字符串有效则为1,否则为0
      
      in_addr_t inet_addr(const char *strptr);                       //返回:若字符串有效则为32位二进制网络字节序的IPv4地址,否则为INADDR_NONE
      
      char *inet_ntoa(struct in_addr inaddr);              //返回:指向一个点分十进制数串的指针

      //还有一个名为inet_network的函数和inet_addr类似,但是其返回的是主机字节序
      in_addr_t inet_network(const char *strptr);

      inet_aton 和 inet_addr 两个函数都是将一个点分十进制字符串转换成一个32位的网络字节序二进制值。inet_aton函数,如果addrptr指针为空,那么该函数仍然对输入的字符串执行有效性检查,但是不存储任何结果。inet_addr函数存在一些问题:所有2^32个可能的二进制值都是有效的ip地址(0.0.0.0-255.255.255.255),但是当出错时函数返回INADDR_NONE常量(通常是一个32位均为1的值),这意味着点分十进制数串255.255.255.255不能由该函数处理,因为它的二进制用来指示该函数失败,inet_addr还存在一个潜在的问题:一些手册声明该函数出错时返回-1而不是INADDR_NONE,这样在对该函数的返回值和一个负常量进行比较时可能会发生问题。如今,inet_addr已经废弃,新的代码应该改用inet_ato函数,更好的解决方法是使用inet_pton函数。另外需要注意的是inet_ntoa函数的参数是in_addr的结构体而不是指针。

    • inet_pton,inet_ntop这一组函数对IPv4和IPv6地址都适用。原型如下:
      #include <arpa/inet.h>
      
      int inet_pton(int family, const char *strptr, void *addrptr);                //返回:若成功则为1,若输入不是有效的表达格式则为0,若出错则为-1
      
      const char *inet_ntop(int family, void *addrptr, char *strptr, size_t len);  //返回:若成功则为指向结果的指针,若出错则为NULL
      //len:INET_ADDRSTRLEN INET6_ADDRSTRLEN
      //inet_ntop函数的strptr不可以是一个空指针,调用者必须为目标存储单元分配内存并制定其大小,调用成功时,这个指针就是该函数的返回值
    • 地址转换函数小结:
                                inet_pton(AF_INET),inet_aton, inet_addr
      点分十进制数     ----------------------------------------------------->          in_addr{}
       IPv4地址       <-----------------------------------------------------       32位二进制IPv4地址
                                inet_ntop(AF_INET),inet_ntoa

       

      #include <stdio.h>
      #include <string.h>
      #include <arpa/inet.h>
      
      int main(int argc, char **argv)
      {
          const char *ip_str = "127.0.0.1";
          char *ip_res;
          in_addr_t addr_t;
          struct in_addr addr;
      
          //1.str -> binary
          inet_aton(ip_str, &addr);
          printf("inet_aton::%x\n", addr);                //1000007f
      
          addr_t = inet_addr(ip_str);
          printf("inet_addr::%x\n", addr_t);              //1000007f
      
          inet_pton(AF_INET, ip_str, (void *)&addr);      //1000007f
          printf("inet_pton::%x\n", addr);
      
          //2.binary -> str
          ip_res = inet_ntoa(addr);
          printf("inet_ntoa::%s\n", ip_res);              //127.0.0.1
      
          inet_ntop(AF_INET, &addr, ip_res, INET_ADDRSTRLEN);
          printf("inet_ntop::%s\n", ip_res);              //127.0.0.1
      
          return 0;
      }
      
      output:
      inet_aton::100007f
      inet_addr::100007f
      inet_pton::100007f
      inet_ntoa::127.0.0.1
      inet_ntop::127.0.0.1

       

posted @ 2016-06-26 01:35  uangyy  阅读(13054)  评论(0编辑  收藏  举报