socket编程基础1(大、小端,IP地址格式转换,通信主机信息获取)

今天我们说说“Pre-网络编程”。内容比较杂,但都是在做网络应用程序开发过程中经常要遇到的问题。


一、大端、小端和网络字节序

小端字节序:little-endian,将低字节存放在内存的起始地址;

大端字节序:big-endian,将高字节存放在内存的其实地址。

      例如,数字index=0x11223344,在大小端字节序方式下其存储形式为:
socket编程基础1(大、小端,IP地址格式转换,通信主机信息获取)_第1张图片

上图一目了然的可以看出大小端字节序的区别。

还有另外一个概念就是网络字节序。网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian方式。注意:X86系列CPU都是小端little-endian字节序,即低字节存低位,高字节存高位。

为此,Linux专门提供了字节转换函数.

unsigned long  int htonl(unsigned long  int hostlong)

unsigned short int htons(unisgned short int hostshort)

unsigned long  int ntohl(unsigned long  int netlong)

unsigned short int ntohs(unsigned short int netshort)

在这四个转换函数中,h代表hostn代表 networks代表shortl代表long htonl()函数的意义是将本机器上的long数据转化为网络上的long。其他几个函数的意义也差不多。

       看个例子:
socket编程基础1(大、小端,IP地址格式转换,通信主机信息获取)_第2张图片

        也就是说对于从网络上接收到的非单子节的基本数据类型数据,首先需要用ntohl(s)将其转换成本地字节序;同理,发往网络的非单子节的基本数据类型数据,首先用htonl(s)将其转换成网络字节序。这里最常见的就是IP地址和端口号。

 

         二、点分十进制格式的IP地址和32bitIP地址

         我们常见的IP地址都是以点分十进制格式表示,例如“172.18.1.231”。而在程序中基本是以如下的结构表示一个IP

struct in_addr {

         __be32     s_addr; //其实就是一个32bit的数字

};

       它和点分十进制格式的IP地址可以通过一组API实现相互转换:

int inet_aton(const char *cp,struct in_addr *inp) 无效的地址cp则返回0;否则返回非0

char *inet_ntoa(struct in_addr in) 将一个32位的IP地址转换成点分十进制字符串。

       这两个函数所要求的struct in_addr{}参数均为网络字节序。

       继续看例子:
socket编程基础1(大、小端,IP地址格式转换,通信主机信息获取)_第3张图片

        “192.168.11.23”转换成数字就是0xc0a80b17,是网络字节序的。如果直接打印,那么本地按照小端字节序来输出,结果为net addr = 170ba8c0,刚好和实际相反。当我们先将其转换成本地字节序,然后再输出时结果就OK了,即host addr = c0a80b17。同理,inet_ntoa()也类似。

        

         三、网络主机名和IP地址的对应关系

         在做网络编程时经常要碰到的一个问题就是根据对方主机名来获取其IP地址,或者根据IP地址反过来解析主机名和其他信息。Linux提供了两个常用的API

struct hostent *gethostbyname(const char *name);

struct hostent *gethostbyaddr(const void *addr, int len, int type);

       这两个函数失败时返回NULL且设置h_errno错误变量,调用hstrerror(h_errno)或者herror("Error");可以得到详细的出错信息。成功时均返回如下结构:        

struct hostent {

     char    *h_name;        /* 主机名*/

     char    **h_aliases;    /* 主机别名的列表*/

     int     h_addrtype;     /* 地址类型,AF_INET或其他*/

     int     h_length;       /* 所占的字节数,IPv4地址都是4字节 */

     char    **h_addr_list;  /* IP地址列表,网络字节序*/

}

#define h_addr  h_addr_list[0]  /*后向兼容 */

       gethostbyname可以将机器名(如www.google.com)转换为一个结构指针,gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针。对于gethostbyaddr函数来说,输入参数“addr”的类型根据参数“type”来确定,目前type仅取AF_INET或AF_INET6。例如,type=2(即AF_INET),则addr就必须为struct in_addr{}类型。

       继续看例子:       

#include <stdio.h>

#include <netdb.h>

#include <error.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

int main(int arg,char** argv){

  struct hostent *host,*host2;

  if(NULL == (host = gethostbyname(argv[1]))){

          herror("Error");

          return 1;

  }

 

  printf("name = %s\n",host->h_name);

  printf("aliases = %s\n",*host->h_aliases);

  printf("add type = %d\n",host->h_addrtype);

  printf("len = %d\n",host->h_length);

  printf("IP=%s\n",inet_ntoa(*(struct in_addr*)host->h_addr));

 

  printf("=================================\n");

  struct in_addr maddr;

  if(0 == inet_aton(argv[2],&maddr)){

          return 0;

  }

 

  char* c = (char*)&maddr;

  printf("org = %x.%x.%x.%x\n",*(c)&0xff,*(c+1)&0xff,*(c+2)&0xff,*(c+3)&0xff);

 

  if(NULL == (host2 = gethostbyaddr(&maddr,4,2))){

          printf("Error:%s\n",hstrerror(h_errno));

          return 1;

  }

  printf("name = %s\n",host2->h_name);

  printf("aliases = %s\n",*host2->h_aliases);

  printf("add type = %d\n",host2->h_addrtype);

  printf("len = %d\n",host2->h_length);

  printf("IP=%s\n",inet_ntoa(*(struct in_addr*)host2->h_addr));

 

  return 0;

}

         运行结果如下:
socket编程基础1(大、小端,IP地址格式转换,通信主机信息获取)_第4张图片

       当我们调用gethostbyaddr根据CU主页的IP地址获取其站点信息时返回的错误是“未知的主机”错误,原因留给大家自己思考吧。这充分说明对了于gethostbyname()函数gethostbyaddr()函数的调用一定要判断其返回值。

你可能感兴趣的:(socket编程基础1(大、小端,IP地址格式转换,通信主机信息获取))