服务器和客户端信息的获取

一、字节序

字节序是由于不同的主处理器和操作系统,对于一个字节的变量在内存中的存放顺序不同而产生。例:2个字节的short int和4个字节的int类型变量都有字节序问题。字节序通常有大端字节序和小端字节序两种。

1.大端字节序和小端字节序

字节序是由于CPU和OS对多字节变量的内存存储顺序不同而产生的。

1.字节序介绍

大于一个字节的变量类型的表示方法有以下两种:

  1. 小端字节序:在表示变量的内存地址的起始地址存放低字节,高字节顺序存放。
  2. 大端字节序:在表示变量的内存地址的起始地址存放高字节,低字节顺序存放。

例:变量的值0xabcd,在大端字节序和小端字节序的系统中二者的存放顺序是不同的,

小端字节序系统的存放如下所示,假设存放值0xabcd的内存地址的起始地址为0,则0xab在地址15-8的地址上,而0xcd在地址7-0的位置上。

在这里插入图片描述
大端字节序系统中的存放顺序如下图,假设存放值0xabcd的内存地址起始地址为0,则0xab在地址7-0的地址上,而0xcd在地址15-8的位置上。
在这里插入图片描述
2.字节序的例子

下面用于上面两图所示变量在内存表示方法,确定系统中的字节序为大端字节序还是小端字节序。

(1)字节序结构。程序首先

#include<stdio.h>
typedef union{
        unsigned short int value;
        unsigned char byte[2];
}to;//字节序结构,建立联合类型to,用于测试字节序,成员value是short类型变量,可以通过成员byte来访问value变量的高字节和低字节。

int main(int argc,char *argv[])
{
//声明变量,将值0xabcd赋给成员变量value。由于在类型to中,value和byte成员共享一块内存,所以可以通过byte的不同成员来访问value的高字节和低字节。
        to typeorder;
        typeorder.value = 0xabcd;
//小端字节序判断。小端字节序的检查通过判断typeorder变量的byte成员高字节和低字节的值来进行:低字节值为0xcd,高字节的值为0xab。
        if(typeorder.byte[0] == 0xcd && typeorder.byte[1]==0xab){
                printf("Low endian byte order"
                        "byte[0]:0x%x,byte[1]:0x%x\n",
                        typeorder.byte[0],
                        typeorder.byte[1]);
        }
//大端字节序判断。大端字节序的检查同样通过判断typeorder变量的byte成员高字节和低字节的值来进行:低字节的值为0xab,高字节的值为0xcd。
        if(typeorder.byte[0] == 0xab && typeorder.byte[1]==0xcd){
                printf("High endian byte oreder"
                        "byte[0]:0x%x,byte[1]:0X%x\n",
                        typeorder.byte[0],
                        typeorder.byte[1]);
                        }
                        return 0;
}

编译运行:
在这里插入图片描述

2.字节序转换函数

主机字节序不能统一,但必须有个统一表示方法。网络字节序是指多字节变量在网络上传输时的表示方法,网络字节序采用高端字节序的表示方法,这样小端字节序的系统通过网络传输变量的时候,需要进行字节序的转换,大端字节序的变量则不需要进行转换。

1.字节序转换函数介绍

Linux操作系统提供了与平台无关的函数进行字节序的转换:

#include<arpa/inet.h>
uint32_t htonl(uint_32_t hostlong);//主机字节序到网络字节序的长整型转换
uint16_t htons(uint16_t hostshort);//主机字节序到网络字节序的短整型转换
uint32_t ntohl(uint32_t netlong);//网络字节序到主机字节序的短长型转换
uint16_t ntohs(uint16_t netshort);//网络字节序到主机字节序的短短型转换
  1. 函数传入的变量为需要转换的变量,返回值为转换后的数值。
  2. 函数命名规则为**“字节序” “to” “字节序” “变量类型”。**上述函数,h:主机字节序;n:network(网络字节序);l:long型变量;s:short型变量。函数htonl()的含义为"主机字节序"转换为"网络字节序",操作的变量为"long型变量"。其他函数类似。
  3. 两个对short类型进行转换的函数为htons()ntohs(),两个对long类型变量进行转换的函数为htonl()ntohl()

注:用户在进行程序设计的时候,需要调用字节序转换函数将主机的字节序转换为网络字节序,至于是否交换字节的顺序,则由字节序转换函数的实现来保证。
也就是说对于用户来说这种转换是透明的,只要将需要在网络上传输的变量调用一遍此类的转换函数进行一次转换即可,不用考虑目标系统的主机字节序方式。

2.字节序转换的方法

  1. 以上函数的作用是对字节进行交换,在大端字节序系统上,上述的字节序转换函数的实际实现可能是空的,即不进行字节序的转换;

  2. 对于小端字节序系统,需要将字节在变量中的顺序进行交换,例如16b的变量交换高低两个字节的位置,32b的变量将0、1、2、 3位置的字节按照0和2、1和3字节进行交换的方法。

在一个小端主机字节序系统上,进行16位数值交换的方法如下图所示:

在这里插入图片描述
进行32位变量的交换方法如下图所示:
在这里插入图片描述

  1. 16b的变量的字节序交换方法,在小端字节序主机系统中,进行转换时,将高地址的字节和低地址的字节进行交换;

  2. 32b的变量进行字节序交换的方法,在小端字节序主机系统中,进行字节序交换时,第0个字节的值与第3个字节的值进行交换,第1个字节的值与第2个字节的值进行交换。

  3. 字节序交换的作用是生成一个网络字节序的变量,其字节的顺序与主机类型和操作系统无关。

  4. 进行网络字节序转换的时候,只要转换一次就可以了,不要进行多次的转换。

  5. 如果进行多次字节序的转换,最后生成的网络字节序的值可能是错误的。

例如,对于主机为小端字节序的系统,进行两次字节序转换的过程如下图所示,经过两次转换,最终的值与最初的主机字节序相同。
在这里插入图片描述

3.字节操控函数

#include<string.s>
void bzero(void *dest,size_t nbytes);//将目标字符串指定数目的字节置为0

void bcopy(const void *src,void *dest,size_t nbytes);//将指定数目的字节从源字节串移到目标字符串。
int bcmp(const void *ptrl,const void *ptr2,size_t nbytes);//比较两个任意的字节串,相同返回值为0,否则非0



#include<string.s>
void *memset(void *dest,int c,size_t len);//把目标字符串数目置为c
void *memcpy(void *dest,const void *src,size_t nbytes);//将指定数目的字节从源字节串移到目标字符串。两个指针参数的顺序是相反的
int memcmp(const void *ptrl,const void *ptr2,size_t nbytes);//比较任意的两个字符串,相同为0,否则非0

4.一个字节序转换的例子

下面对16位数值和32位数值进行字节序转换,每种类型的数值进行两次转换,最后打印结果。

#include<stdio.h>
#include<arpa/inet.h>

#define BITS16 16
#define BITS32 32


//联合类型的变量类型,用于测试字节序
//成员value的高低端字节可以由成员type按字节访问

//16位字节序转换的结构
typedef union{
        unsigned short int value;//16位short类型变量value
        unsigned char byte[2];//char类型数组,共16位
}to16;

//32位字节序转换的结构
typedef union{
        unsigned long int value;//32位unsigned long类型变量
        unsigned char byte[4];//char类型数组,共32位
}to32;


//此函数用于打印变量值,打印的方式从变量存储空间的第一个字节开始,按照字节进行打印。
void showvalue(unsigned char *begin,int flag)//变量的地址指针begin,字长标志flag。
{
        int num=0,i=0;
        if(flag == BITS32){
                num=2;
        }else if(flag==BITS32){
                num=4;
        }

        for(i=0;i<num;i++)
        {
                printf("%x",*(begin+i));
        }
        printf("\n");
}
int main()
{
		//v16_orig是16位变量的原始值,v16_turn1是变量进行第一次字节序转换后的结果,
		//v16_turn2是变量进行第二次字节序转换后的结果,
        to16 v16_orig,v16_turn1,v16_turn2;
        to32 v32_orig,v32_turn1,v32_turn2;
        v16_orig.value = 0xabcd;//赋值
        v16_turn1.value = htons(v16_orig.value);//第一次
        v16_turn2.value = htons(v16_turn1.value);//第二次
        v32_orig.value = 0x12345678;//赋值
        v32_turn1.value = htonl(v32_orig.value);//第一次
        v32_turn2.value = htonl(v32_turn1.value);//第二次

        printf("16 host to network byte order change:\n");
        printf("\torig:\t");showvalue(v16_orig.byte,BITS16);

        printf("\t1 times:");showvalue(v16_turn1.byte,BITS16);
        printf("\t2 times:");showvalue(v16_turn2.byte,BITS16);


        printf("32 host to network byte order change:\n");
        printf("\toring:\t");showvalue(v32_orig.byte,BITS32);

        printf("\t1 times:");showvalue(v32_turn1.byte,BITS32);
        printf("\t2 times:");showvalue(v32_turn2.byte,BITS32);

        return  0;
}

编译运行:
g)

二、字符串IP地址和二进制IP地址的转换

可以理解的IP地址表达方式是类似127.0.0.1这样的字符串;而计算机理解的则是像Ox11111111000000000000000000000001 (127.0.0.1)这样表达的IP地址方式。在网络程序的设计中,经常需要进行字符串表达方式的IP地址和二进制的IP地址之间的转换。

1.inet_xxx()函数

Linux操作系统有一组函数用于网络地址的字符串形式和二进制形式之间的转换,其形式为inet_xxx()。函数的原型如下:

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int inet_aton(const char *cp,struct in_addr *inp);//将点分四段式的IP地址转换为地址结构in_addr值

in_addr_t inet_addr(const char *cp);//将字符串转换为in_addr值
in_addr_t inet_network(const char *cp);//字符串地址的网络部分转为in_addr类型

char *inet_ntoa(struct in_addr in);//将in_addr结构转换为字符串
struct in_addr inet_makeaddr(int net,int host);//将网络地址和主机地址合成为IP地址

in_addr_t inet_lnaof(struct in_addr in);//获得地址的主机部分
in_addr_t inet_netof(struct in_addr in);//获得地址的网络部分

1.inet_aton()函数

inet_aton()函数将在cp中存储的点分十进制字符串类型的IP地址,转换为二进制的IP地址,转换后的值保存在指针inp指向的结构structer in_addr中。当转换成功时返回值为0;当传入的地址非法时,返回值为0。

2.inet_addr()函数

  1. inet_addr()函数将cp中存储的点分十进制字符串类型的IP地址转换为二进制的IP地址,IP地址是以网络字节序表达的。如果输入的参数非法,返回值为INADDR_NONE(通常为-1),否则返回值为转换后的IP地址。
  2. 这个函数是函数inet_aton()缩减版,由于值-1(1111111111111111)同时可以理解为合法IP地址255.255.255.255的转换结果,所以不能使用这个函数转换IP地址255.255.255.255。

3.inet_network()函数

inet_ network()函数将 cp 中存储的点分十进制字符串类型的 IP地址,转换为二进制的IP 地址,IP 地址是以网络字节序表达的。
当成功时返回32位表示 IP 地址,失败时返回值为-1。参数 cp 中的值有可能采用以下形式:

  1. a.b.c.d: 这种形式指定了 IP 地址的全部4 个段,是一 个完全的IP 地址转换,这种情况下函数 inet_network()与函数 inet_addr()完全一 致

  2. a.b.c: 这种形式指定了 IP 地址的前 3个段,a.b解释为 IP 地址的前 16位,c解释为后面的 16位。例如 172.16.888会将 888解释为IP 地址的后 16位。

  3. a.b: 这种形式指定了IP 地址的前两个段,a为IP 地址的前 8位,b解释为后面的24 位。例如,172.888888会将 888888解释为 IP 地址的后 3段。

  4. a: 当仅为一部分时,a的值直接作为IP 地址,不做字节序转换。

4.inet_ntoa()函数

1.inet_ ntoa()函数将一 个参数in所表示的Internet地址结构转换为点分十进制的4段式字符串 IP 地址,其形式为a.b.c.d
2. 返回值为转换后的字符串指针,此内存区域为静态的,有可能会被覆盖,因此函数并不是线程安全的。
3. 例如,将二进制的 IP 地址 0x1000000000000001使用函数 inet_ntoa()转换为字符串类型的结果为 127.0.0.1

5.inet_makeaddr()函数

一个主机的IP地址分为网络地址和主机地址,inet_makeaddr()函数将主机字节序的网络地址net和主机地址host合并成一个网络字节序的IP地址。

下面将网络地址127和主机地址1合并成一个IP地址127.0.0.1

unsigned long net,hst;
net=0x0000007F;host=0x00000001;
struct in_addr ip = inet_makeaddr(net,hst);

6.inet_lnaof()函数

inet_lnaof()函数返回IP地址的主机部分。例如,下面的地址返回IP地址127.0.0.1的主机部分:

const char *addr = "127.0.0.1";
unsigned long ip = inet_network(addr);
unsigned long host_id = inet_lnaof(ip);

7.inet_netof()函数

inet_netof()函数返回IP地址的网络部分,例如,下面的例子返回IP地址127.0.0.1的网络部分。

const char *addr = "127.0.0.1";
unsigned long ip = inet_network(addr);
unsigned long network_id = inet_netof(ip);

8.结构struct in_addr

结构struct in_addr在文件<netinet/in.h>中定义,结构in_addr有一个unsigned long int 类型的成员变量s_addr。通常所说的IP地址的二进制形式保存在变量s_addr中。结构struct in_addr的原型如下:

struct in_addr{
unsigned long int s_addr;//IP地址
}

注:

  1. 函数inet_addr() , inet_ network()的返回值为-1时表示错误,这占用了255.255.255.255的值,因此可能存在缺陷。
  2. 函数inet_ ntoa()的返回值为一个指向字符串的指针,这块内存函数inet_ntoa()每次调用都会重新覆盖,因此函数并不安全,可能存在某种隐患。
  3. 将字符串IP地址转换为in_addr时,注意字符串中对IP地址的描述,上述函数假设宇符串中以0开始表示八进制,以0x开始表示十六进制,将按照各进制对字符串进行解析。例如IP地址192.168.000.037最后一段的037表示的是八进制的数值,即相当于192.168.0.31

2.inet_pton()和inet_ntop()函数

inet_pton()函数和inet_ntop()函数是一套安全的协议无关的地址转换函数。所谓的"安全"是相当于inet_aton()函数的不可重入性来说。这两个函数都是重入的,并且这些函数支持多种地址类型,包括IPv4和IPv6。

1.inet_pton()函数

  1. inet_pton()函数将字符串类型的IP地址转换为二进制类型,其原型如下。
  2. 第1个参数af表示网络类型的协议族,在IPv4下的值为AF_INET;
  3. 第2个参数src表示需要转换的字符串;
  4. 第3个参数dst指向转换后的结果,在IPv4下,dst指向结构struct in_ addr的指针。
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
int inet_pton(int af,const char *src,void *dst);

返回值为-1时,通常由af所指定的协议族不支持造成的,此时erron的返回值为EAFNOSUPPORT;当函数的返回值为0时,表示src指向的值不是合法的IP地址;当函数的返回值为正值时,表示转换成功。

2.inet_ntop()函数

inet_ntop()函数将二进制的网络IP地址转换为字符串,函数原型如下。

  1. 第1个参数af表示网络类型的协议族,在IPv4下的值为AF_INET

  2. 第2个参数src为需要转换的二进制IP地址,在IPv4下,src指向一个struct in_addr结构类型的指针;

  3. 第3个参数dst指向保存结果缓冲区的指针;

  4. 第4个参数cnt的值是dst缓冲区的大小。

#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
const char *inet_ntop(int af,const void *src,char *dst,socklen_t cnt);

inet_ntop()函数返回一个指向dst的指针。当发生错误时,返回NULL。当af设定的协议族不支持时,errno设置为EAFNOSUPPORT;当dst缓冲区过小时候errno的值为ENOSPC

3.sock_ntop函数

inet_ntop函数要求调用者传递一个指向某个二进制地址的指针,而地址通常包含一个套接字地址结构中,这就要求必须知道这个结构的格式和地址族。

//IPv4
struct sockaddr_in add;
inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));
//IPv6
struct sockaddr_in6 addr6;
inet_ntop(AF_INET6,&addr6.sin_addr,str,sizeof(str));
//代码都与协议相关了。


//解决这个问题
//以指向某个套接字地址结构的指针为参数,查看该结构的内部,然后调用适当的函数返回地址的表达式
char *sock_ntop(const struct sockaddr *sockaddr,socklen_t addrlen);
//sockaddr:指向一个长度为addrlen的套接字地址结构


//IPv4:INET_ADDRSTRLEN+6字节
//IPv6:INET6_ADDRSTRLEN+8字节

//AF_INE情况:
char * sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
    char		portstr[8];
    static char str[128];		/* Unix domain is largest */

	switch (sa->sa_family) {
	case AF_INET: {
		struct sockaddr_in	*sin = (struct sockaddr_in *) sa;

		if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
			return(NULL);
		if (ntohs(sin->sin_port) != 0) {
			snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
			strcat(str, portstr);
		}
		return(str);
	}

其它IPv4和IPv6之间的移植函数:
在这里插入图片描述

4.使用地址转换函数的例子

对函数的重入性能进行了测试,测试结果表明函数inet_ntoa()、inet_addr()是不可重入的。

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
int main(int argc,char *argv[])
{
//初始化必要设置,例如测试的字符串IP地址,用户保存结果的网络地址结构和IP地址结构等参数。
        struct in_addr ip,local,network;
        char addr1[]="192.168.1.1";//a.b.c.d类型的网络地址字符串
        char addr2[]="255.255.255.255";//二进制值为全1的IP地址对应的字符串
        char addr3[]="192.16.1";//a.b.c类型的网络地址字符串
        char *str=NULL,*str2=NULL;

        int err = 0;
        err = inet_aton(addr1,&ip);//将字符串IP地址192.168.1.1转换成二进制IP地址,并将结果打印出来。
        if(err){
                printf("inet_aton:string %s value is:0x%x\n",addr1,ip.s_addr);
        }else{
                printf("inet_aton:string %s error\n",addr1);
        }

        ip.s_addr = inet_addr(addr1);//将字符串IP地址转换为二进制IP地址,先测试192.168.1.1,在测试255.255.255.255
        if(err !=-1){
                printf("inet_addr:string %s value is:0x%x\n",addr1,ip.s_addr);
        }else{
                printf("inet_addr:string %s error\n",addr1);
        };
        ip.s_addr = inet_addr(addr2);
        if(ip.s_addr !=-1){
                printf("inet_addr:string %s value is:0x%x\n",addr2,ip.s_addr);
        }else{
                printf("inet_addr:string %s error\n",addr2);
        };
        
 //测试函数inet_ntoa,先测试IP地址192.168.1.1对应的字符串str,然后测试IP地址255.255.255.255对应的字符串IP地址str2。两个IP地址都转换完毕后,一起打印值,这时str和str2相同。
 ip.s_addr = 192<<24|168<<16|1<<8|1;
        str = inet_ntoa(ip);
        ip.s_addr = 255<<24|255<<16|255<8|255;
        str2 = inet_ntoa(ip);
        printf("inet_ntoa:ip:0x%x string1 %s,pre is:%s\n",ip.s_addr,str2,str);

//将字符串IP地址转换为二进制IP地址,使用的字符串为192.16.1
        ip.s_addr = inet_addr(addr3);
        if(err !=-1){
                printf("inet_addr:string %s value is:0x%x\n",addr3,ip.s_addr);
        }else{
                printf("inet_addr:string %s error\n",addr3);
        };
        str = inet_ntoa(ip);
        printf("inet_ntoa:string %s ip:0x%x\n",str,ip.s_addr);

        inet_aton(addr1,&ip);
        local.s_addr = htonl(ip.s_addr);
        local.s_addr = inet_lnaof(ip);
        str = inet_ntoa(local);
        printf("inet_lnaof:string %s ip:0x%x\n",str,local.s_addr);
//获得本机地址,这个函数只取四段式IP地址的前三段。
        network.s_addr = inet_netof(ip);
        printf("inet_netof:value:0x%x\n",network.s_addr);
        return 0;
}

编译运行:
在这里插入图片描述
上述结果的输出信息"inet_ntoa:ip:Oxffffffff string1 255.255.255.255,pre is:255.255.255. 255 ", 表明函数inet_ntoa在进行二进制IP地址字符串IP地址的转换过程中是不可重入的,这个函数转换两个不同的IP地址得到了同一 个结果。

由于函数 的实现没有考虑重入的特性,用同一个缓冲区保存了临时结果。函数 inet_addr 同样存在不可重入的问题 。此类函数在调用之后,需要立即将结果取出,没有取出结果之前不能进行同样函数的调用。

5.使用函数inet_pton()和函数inet_ntop()的例子

使用函数inet_pton()将字符串转换为二进制,使用inet_ntop()将二进制IP地址转换为字符串。

#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>

#define ADDRLEN 16

int main()
{
        struct in_addr ip;
        char IPSTR[]="192.168.1.1";//网络地址字符串
        char addr[ADDRLEN];//保存转换后的字符串IP地址,16个字节大小
        const char*str = NULL;
        int err=0;//返回值

		//测试函数inet_pton转换192.168.1.1为二进制形式
        err = inet_pton(AF_INET,IPSTR,&ip);//字符串转换为二进制
        if(err>0){
                printf("inet_pton:ip,%s value is:0x%x\n",IPSTR,ip.s_addr);
        }
		//inet_ntop转换192.168.1.1位字符串
        ip.s_addr = htonl(192<<24|168<<16|12<<8|255);
        //将二进制网络字节序192.168.12.255转换为字符串
        str = (const char*)inet_ntop(AF_INET,(void*)&ip,(char*)&addr[0],ADDRLEN);
        if(str){
                printf("inet_ntop:ip,0x%x is %s\n",ip.s_addr,str);
        }
        return 0;
}

编译运行:
在这里插入图片描述

三、套接字描述符判定函数issockettype()

套接字文件描述符从形式上与通用文件描述符没有区别,判断 一个文件描述符是否是一个套接字描述符可以通过如下的方法实现:

  1. 先调用函数 fstat()获得文件描述符的模式,然后将模式的S_IFMT部分与标识符S_IFSOCK比较,就可以知道 一 个文件描述符是否为套接字描述符。

下面是套接字描述符判定的实例代码。程序代码先构建一个用于测试是否为套接字文件描述符的函数 issockettype(), 在主函数中对标准输入和构建后的套接字文件描述符进行是否套接字文件描述符的判断。

1.进行文件描述符判定的函数issockettype()

#include<sys/socket.h>
#include<stdio.h>
#include<sys/stat.h>


//issockettype先获得描述符的状态,保存在变量st中,
//将st的成员st_mode与S_IFMT进行**与运算**后获取文件描述符的模式。
//判断上述值是否与S_IFSOCK相等,就可以知道文件描述符是否为套接字文件描述符
int issockettype(int fd)
{
        struct stat st;
        int err = fstat(fd,&st);

        if(err<0){
                return -1;
        }

        if((st.st_mode & S_IFMT) == S_IFSOCK){
        return 1;
        }else{
                return 0;
        }
}

//先判断标准输入是否为套接字文件描述符,将判断结果打印出来,
//然后建立一个套接字s,使函数issocktype()对s进行判断
//并将判断结果打印出来
int main()
{
        int ret = issockettype(0);//查询标准输入是否套接字描述符
        printf("value %d\n",ret);

        int s = socket(AF_INET,SOCK_STREAM,0);//建立套接字描述
        ret = issockettype(s);//查询是否为套接字描述
        printf("value %d\n",ret);//输出结果
        return 0;
}

编译运行:

在这里插入图片描述
输出结果表明标准输入不是套接字描述符,而建立的SOCK_STREAM类型的套接字是套接字描述符。
value 0不是套接字描述符,value 1是套接字描述符

四、IP地址与域名之间的相互转换

在实际的使用中,经常有只知道主机的域名而不知道主机名对应的 IP 地址的情况,而socket的 API 均为基于 IP 地址,所以如何进行主机域名IP 地址之间的转换是十分必要的。

1.DNS原理

  1. DNS (Domain Name System)是 "域名系统” 的英文缩写,域名系统是一 种树形结构,按照区域组成层次性的结构,表示计算机名称和 IP 地址的对应情况。

  2. DNS 用于TCP/IP 的网络,用比较形象化的友好命名来代替枯燥的 IP 地址,方便用户记忆。

  3. DNS 的功能就是在主机的名称和 IP 地址之间担任翻译工作。

①.DNS查询过程

  1. 在实际应用中,经常有进行DNS转换的过程,例如当使用Web浏览器时,在地址栏输入域名,浏览器就可以自动打开远程主机上的内容,这里就有DNS的主机在起作用。

  2. 本地主机将用户输入的域名通过DNS主机翻译成对应的IP地址,然后通过IP地址访问目标主机。

  3. 由于程序仅能识别IP地址,而IP地址又不容易被记忆,所以为了方便人类记忆而又方便程序访问,出现了DNS。

查询DNS地址过程的示意图所示:
在这里插入图片描述

②.DNS的拓扑结构

  1. 顶级域名服务器下分为多个二级域名服务器,二级域名服务器下又分为多个下级的域名服务器,每个域名服务器都下辖了一 些主机。

  2. 如果一个主机需要查询一 个域名的IP 地址,需要向本地的域名服务器查询。

  3. 当本地域名服务器不能查到时,就向上一 级的域名服务器查询;

  4. 当二级域名服务器不能查询到域名对应的主机信息,会向顶级域名服务器查询;

  5. 如果顶级域名服务器不能识别该域名,则会返回错误。

DNS按照树形的结构构造,如下图所示:

在这里插入图片描述
本地主机查询目标机的DNS查询过程如下图所示:
在这里插入图片描述

2.获取主机信息的函数

gethostbyname()函数和gethostbyaddr()函数都可以获得主机的信息:

  • gethostbyname()函数通过主机的名称获得主机的信息,

    • gethostbyaddr()函数通过IP地址获得主机的信息。

①.gethostbyname()函数

gethostbyname()函数的原型如下,它根据主机名获取主机的信息,例如www.sina. com.en,
使用gethostbyname("www.sina.com.cn")可以获得主机的信息。 这个函数的参数name是要查询的主机名,通常是DNS的域名。

#include<netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char y*name);

此函数返回值是一个指向结构 struct hostent 类型变量的指针,当为NULL时,表示发生错误,错误类型可以通过errno 获得,错误的类型及含义如下所述:

  1. HOST_NOT_FOUND : 查询的主机不可知,即查不到相关主机的信息。

  2. NO_ADDRESSNO_DATA: 请求的名称合法但是没有合适的IP地址。

  3. NO_RECOVERY : 域名服务器不响应。

  4. TRY_AGAIN: 域名服务器当前出现临时性错误,稍后再试。

结构struct hostent的原型定义如下:

struct hostent{
	char *h_name;//主机的正式名称
	char **h_aliases;//别名列表
	int	   h_addrtype;//主机地址类型
	int 	h_length;//地址长度
	char  **h_addr_list;//地址列表
	}
#define h_addr h_addr_list[0] //为了向前兼容定义的宏

如下图所示,结构体struct hostent由成员h_name、h_aliases、h_addrtype、h_lengthh_addr_list组成。

在这里插入图片描述

  1. 成员h_name是主机的官方名称,如新浪的www .sina.com.cn 。
  2. 成员h_aliases是主机的别名,别名可能有多个,所以用一个链表表示,链表的尾部是一个NULL指针。
  3. 成员h_addrtype是主机的地址类型,AF_INET表示IPv4的IP地址,AF_INET6表示IPv6的IP地址。
  4. 成员h_length是IP地址的长度,对于IPv4来说为4,即4个字节。
  5. 成员h_addr_list是主机的IP地址的链表,每个都为h_length长,链表的尾部是一个NULL指针。

②.gethostbyaddr()函数

此通过查询IP地址来获得主机的信息。

  1. 第1个参数addr在IPv4的情况下指向一个structin_addr的地址结构,用户需要查询主机的IP地址填入到这个参数中;

  2. 第2个参数len表示第一个参数所指区域的大小,在IPv4情况下为sizeof(struct in_addr),即32位;

  3. 第3个参数type指定需要查询主机IP地址的类型,在IPv4的情况下为AF_INET。其返回值和错误代码含义与函数gethostbyname()相同。

#include<netdb.h>
#include<sys/socket.h>
struct hostent *gethostbyaddr(const void *addr,int len,int type);

注:函数gethostbyname()和gethostbyaddr()是不可重入的函数,由于传出的值为一块静态的内存地址,当另一次查询到来的时候,这块区域会被占用,所以在使用的时候要小心。

3.使用主机名获取主机信息的例子

下面查询www.sina.com.cn的信息,并将主机的信息打印出来。

#include<netdb.h>
#include<string.h>
#include<stdio.h>
#include<arpa/inet.h>

int main()
{
        char host[]="www.sina.com.cn";
        struct hostent *ht=NULL;
        char str[30];
        ht = gethostbyname(host);
        if(ht){
                int i = 0;
                printf("get the host:%s addr\n",host);//原始域名
                printf("name:%s\n",ht->h_name);//名称
	//协议族AF_INET为IPv4或者AF_INET6为IPv6
                printf("type:%s\n",ht->h_addrtype==AF_INET?"AF_INET":"AF_INET6");
                printf("legnth:%d\n",ht->h_length);//IP地址长度
				//打印IP地址
                for(i=0;;i++){
                        if(ht->h_addr_list[i] !=NULL){//不是IP地址数组的结尾
                printf("IP:%s\n",inet_ntop(ht->h_addrtype,ht->h_addr_list[i],str,30));//打印IP地址
         }else{//达到结尾
        break;//退出for循环
      }
}
   //打印域名地址
  for(i=0;;i++){//循环
 if(ht->h_aliases[i] !=NULL){//没有到域名数组的结尾
 printf("alias %d:%s\n",i,ht->h_aliases[i]);//打印域名
    }else{//结尾
    break;//退出循环
    }
   }
  }
return 0;
 }

编译运行:
在这里插入图片描述
由结果可知:
主机的类型为AF_INET即IPv4类型的地址,IP地址的长度为4,即4个字节(32位)。

主机www.sina.com.cn的主机名如下:

在这里插入图片描述
主机的IP共有16个地址,如下:

在这里插入图片描述

4.函数gethostbyname()不可重入的例子
#include<netdb.h>
#include<string.h>
#include<stdio.h>
#include<arpa/inet.h>

int main(int argc,char *argv[])
{

        struct hostent *ht=NULL;
        char host[]="www.sina.com.cn";
        char host1[]="www.sohu.com";
        char str[30];
        struct hostent *ht1 = NULL,*ht2=NULL;

        ht1 = gethostbyname(host);
        ht2 = gethostbyname(host1);

        int j = 0;
        for(j=0;j<2;j++){
                if(j==0)
                ht = ht1;
        else
                ht = ht2;
        if(ht){
                int i = 0;
                printf("get the host:%s addr\n",host);
                printf("name:%s\n",ht->h_name);

printf("type:%s\n",ht->h_addrtype==AF_INET?"AF_INET":"AF_INET6");
                printf("length:%d\n",ht->h_length);

                for(i=0;;i++){
                        if(ht->h_addr_list[i] !=NULL){
                                printf("IP:%s\n",inet_ntop(ht->h_addrtype,ht->h_addr_list[i],str,30));
                        }else{
                                break;
                        }
                }
                for(i=0;;i++){
 if(ht->h_aliases[i] !=NULL){
                                printf("alias %d:%s\n",i,ht->h_aliases[i]);
                        }else{
                                break;
                        }
                }
        }
}
        return 0;
}
                                                 

编译运行结果:

在这里插入图片描述

  • 从结果中可以看出,gethostbyname()函数是不可重入的,输出的结果都是关于www. sohu.com的信息,关于www.sina.com.cn主机信息都已经被www.sohu.com的信息覆盖了。
    • 函数gethostbyname()的不可重入性在进行程序设计的时候要注意,使用函数gethostbyname()进行主机信息查询的时候,函数返回后,要马上将结果取出,否则会被后面的函数调用过程覆盖。

五、协议名称处理函数

为了方便操作, Linux 提供了一 组用于查询协议的值及名称的函数。

1.xxxprotoxxx()函数

协议族处理函数如下几个,可通过协议的名称、编号等获取协议类型。

#include <netdb.h> 
struct protoent *getprotoent(void); /*从协议文件中读取一 行*/
struct protoent *getprotobyname(const char name);/*从协议文件中找到匹配项*/
struct protoent *getprotobynumber(int proto); /*按照协议类型的值获取匹配项*/
void setprotoent(int stayopen); /*设置协议文件打开状态*/
void endprotoent(void); /*关闭协议文件*/

函数对文件/etc/protocols中的记录进行操作,文件中记录了协议的名称、值和别名等值,与结构struct protoent的定义一致。结构protoent的定义如下:

struct protoent
{
	char *p_name;//协议的官方名称
	char **p_aliases;//别名列表
	int p_proto;//协议的值
	};

如图所示,成员p_name指向一块内存,其中存放了协议的官方名称。成员p_aliases指向的区域是一个列表,存放了协议的别名,最后以NULL结尾。
在这里插入图片描述

  1. 成员p_name为指向协议名称的指针。

  2. 成员p_aliases是指向别名列表的指针,协议的别名是一个字符串。

  3. 成员p_proto是协议的值。

  4. 函数 getprotoent()从文件/etc/protocols 中读取一 行并且返回一 个指向 struct protoent 的指针,包含读取一 行的协议。需要事先打开文件/etc/protocols

  5. 函数getprotobyname() 按照输入的协议名称name,匹配文件/etc/protocols 中的选项,返回一 个匹配项。

  6. 函数 getprotobynumber()按照输入的协议值 proto,匹配文件/etc/protocols 中的选项,返回一个匹配项。

  7. 函数setprotoent()打开文件/etc/protocols, 当stayopen1的时候,在调用函数getprotobynam e()或者函数getprotobynumber()查询协议时,并不关闭文件。

  8. 函数endprotoent()关闭文件/etc/protocols

函数getprotoent()、getprotobyname()和getprotobynumber()在调用成功时,返回一个指向结构struct protoent的指针;失败时,返回NULL。

2.使用协议族函数的例子

如下例子按照名称查询一 组协议的项目,首先用 setprotoent(1)打开文件 /etc/protocols , 然后使用函数getprotobyname()查询函数并显示出来,最后使用函数endprotoent()关闭件/etc/protocols。代码如下所示。

#include<netdb.h>
#include<stdio.h>

//此函数将一个给定结构protoent中的协议打印出来,并判断是否有别名,
//将本协议所有相关的别名都打印出来,最后打印协议值。
void display_protocol(struct protoent*pt)
{
    int i=0;
    if(pt){//合法的指针
        printf("protocol name:%s,",pt->p_name);//协议的官方名称
        if(pt->p_aliases){//别名不能为空
            printf("alias name:");//显示别名
           while(pt->p_aliases[i]){//列表没有到尾
               printf("%s",pt->p_aliases[i]);//显示当前别名
               i++;//下一个别名
           }
        }
        printf(",value:%d\n",pt->p_proto);//协议值
    }
}

int main(int argc,char *argv[])
{
    int i=0;
    const char *const protocol_name[]={//查询协议名称
        "ip",
        "icmp",
        "igmp",
        "ggp",
        "ipencap",
        "st",
        "tcp",
        "egp",
        "igp",
        "pup",
        "udp",
        "hmp",
        "xns-idp",
        "rdp",
        "iso-tp4",
        "xtp",
        "ddp",
        "idpr-cmtp",
        "ipv6",
        "ipv6-route",
        "ipv6-frag",
        "idrp",
        "rsvp",
        "gre",
        "esp",
        "ah",
        "skip",
        "ipv6-icmp",
        "ipv6-nonxt",
        "ipv6-opts",
        "rspf",
        "vrntp",
        "eigrp",
        "ospf",
      "ax.25",
        "ipip",
        "etherip",
        "encap",
        "pim",
        "ipcomp",
        "vrrp",
        "12tp",
        "isis",
        "sctp",
        "fc",
        NULL};
          setprotoent(1);
          //在使用函数getprotobyname()时不关闭文件/etc/protocols
        while(protocol_name[i] !=NULL){//没有到数组protocol_name的结尾
            struct protoent *pt = getprotobyname((const char*)&protocol_name[i][0]);//查询协议
            if(pt){//成功
                display_protocol(pt);//显示协议项目
            }
            i++;//移到数组protocol_name的下一个
        };
        endprotoent();//关闭文件/etc/protocols
        return 0;
}

如下表所示,程序运行的结果如下:

在这里插入图片描述

编译运行:
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值