第8章 域名与网络地址

8.1域名系统

DNS是对IP地址和域名进行相互转换的系统,其核心是DNS服务器。

1.什么是域名

提供服务的服务器端也是通过IP地址区分,几乎不可能以非常难记忆IP地址形式交换服务器信息。因此将容易记忆、容易表述的域名分配取代IP地址

2.DNS服务器

域名是给服务器的虚拟地址,并非实际地址(IP地址)。因此需要DNS服务器将域名转换为IP地址。所有计算机都记录着默认的DNS服务器地址,通过这个默认的DNS服务器获得相应域名的IP地址信息。

例如在浏览器地址栏输入域名,这个浏览器通过默认的DNS服务器获得该域名的IP地址信息。

注:

除非商用,一般不会改变服务器域名,但是会频繁的改变IP地址。
ping命令可以验证IP数据报是否到达目的地,但也可以经过“域名到IP地址”的过程,因此可以通过ping查看某域名IP。
nslookup命令可以查看计算机注册的默认的DNS服务器地址

计算机内置的默认DNS服务器并不知道网络上所有域名的IP地址信息。如果该DNS服务器无法解析,会询问其他DNS服务器并提供给用户。查询方式主要为递归查询,迭代查询。 递归查询 1、主机首先向其本地域名服务器进行递归查询。 2、本地域名服务器收到递归查询的委托后,也采用递归查询的方式向某个根域名服务器查询。 3、根域名服务器收到递归查询的委托后,也采用递归查询的方式向某个顶级域名服务器查询。 4、顶级域名服务器收到递归查询的委托后,也采用递归查询的方式向某个权限域名服务器查询。 当查询到域名对应的IP地址后,查询结果会在之前受委托的各域名服务器之间传递,最终传回给用户主机。 迭代查询 1、主机首先向其本地域名服务器进行递归查询。 2、本地域名服务器采用迭代查询,它先向某个根域名服务器查询。 3、根域名服务器告诉本地域名服务器,下一次应查询的顶级域名服务器的IP地址。 4、本地域名服务器向顶级域名服务器进行迭代查询。 5、顶级域名服务器告诉本地域名服务器,下一次应查询的权限域名服务器的IP地址。 6、本地域名服务器向权限域名服务器进行迭代查询。 7、权限域名服务器告诉本地域名服务器所查询的域名的IP地址。 8、本地域名服务器最后把查询的结果告诉主机。

8.2IP地址和域名之间转换

1.程序中有必要使用域名吗

向程序用户提供便利的运行方法,因此,程序不能像之前那样输入IP地址信息和端口信息,那么怎么样传递给程序信息?直接将地址信息写入程序代码?系统运行时,保持IP地址不容易。特别是依赖ISP服务提供者维护IP时,系统等相关原因可能会随时导致IP地址变更。ISP也许会维持原有IP,但程序不能完全依赖于这一点。每次发生地址变更,用户都得去重新下载程序,或者提供给用户源码,让他们自行更改(笑),这怎么可能?IP地址变更频率要比域名高,有时域名一旦注册可能永久不会改变,所以利用域名编写程序更好点。每次运行程序就根据域名获得IP地址,在接入服务器,这就不需要依赖服务器IP地址。

2.利用域名获取IP地址

#include<netdb.h>
struct hostent * geyhostbyname(const char * hostname)
成功时返回hostent结构体地址,失败时返回NULL

hostent结构体

struct hostent
{
    char * h_name;
    char ** h_aliases;
    int h_addrtype;
    int h_length;
    char ** h_addr_list;
}
h_name:     官方域名信息,也就是某一主页,虽然有些著名公司的域名并没使用官方域名注册
h_aliases:  可以通过多个域名访问同一个主页。同一IP可以绑定多个域名,这些域名就放在h_aliases指向的数据结构中
h_addrtype: gethostbyname支持IPv4和IPv6,这个变量存储保存在h_addr_list中的IP地址的地址族信息。若为IPv4就是AF_INET
h_length:   保存IP地址长度。IPv4四个字节,IPv6十六个字节
h_addr_list:通过此变量以整型形式保存域名对应的IP地址。同一个IP地址也可能对应多个域名。利用多个服务器进行负载。

hostent结构

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int i;
	struct hostent *host;
	if(argc!=2) {
		printf("Usage : %s <addr>\n", argv[0]);
		exit(1);
	}
	
	host=gethostbyname(argv[1]);
	if(!host)
		error_handling("gethost... error");

	printf("Official name: %s \n", host->h_name);//输出官方域名
	
	for(i=0; host->h_aliases[i]; i++)
		printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);//获取字符串首地址可以直接输出
	
	printf("Address type: %s \n", 
		(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");

	for(i=0; host->h_addr_list[i]; i++)
		printf("IP addr %d: %s \n", i+1,
					inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

h_addr_list指向字符串指针数组(由多个字符串地址构成的数组)。但是字符串指针数组中的元素实际保存的是in_addr结构体变量地址而非字符串变量地址。所以要强制转换。

重要: 为什么用char* 而不是in_addr*

hostent结构体的成员h_addr_list指向的数组类型并不是in_addr结构体的指针数组,而是采用了char指针。
hostent并非只为IPv4准备。h_addr_list指向的数组也可以保存IPv6信息。因此考虑通用性,声明为char指针类型数组。
那么为什么不用void指针类型?
指针对象不明确时,确实更适合用void指针类型。但是目前学的套接字相关函数都是在void指针标准化之前定义的,
而当时,如果无法明确指针类型时采用的是char指针。

3.利用IP地址获取域名

根据IP地址获取包含IP地址在内的域相关信息

#include<netdb.h>
struct hostent * gethostbyaddr(const char * addr,socklen_t len,int family)
addr        含有IP地址信息的in_addr结构体指针。为了同时传递IPv4地址之外的其他信息,该变量的类型声明为char指针
len         向第一个参数传递的地址信息字节数,IPv4为4,IPv6为6
family      传递地址族信息,IPv4为AF_INET,IPv6为AF_INET6
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int i;
	struct hostent *host;
	struct sockaddr_in addr;
	if(argc!=2) {
		printf("Usage : %s <IP>\n", argv[0]);
		exit(1);
	}

	memset(&addr, 0, sizeof(addr));
	addr.sin_addr.s_addr=inet_addr(argv[1]);
	host=gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
	if(!host)
		error_handling("gethost... error");

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

	for(i=0; host->h_aliases[i]; i++)
		printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
	
	printf("Address type: %s \n", 
		(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");

	for(i=0; host->h_addr_list[i]; i++)
		printf("IP addr %d: %s \n", i+1,
					inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));	
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

8.3基于Windows的实现

#include<winsock2.h>
struct hostent * gethostbyname(const char * name)

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

两个函数的演示 gethostbyname_win

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
void ErrorHandling(char *message);

int main(int argc, char *argv[])
{
	WSADATA wsaData;
	int i;
	struct hostent *host;
	if(argc!=2) {
		printf("Usage : %s <addr>\n", argv[0]);
		exit(1);
	}
	if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
		ErrorHandling("WSAStartup() error!"); 
	
	host=gethostbyname(argv[1]);
	if(!host)
		ErrorHandling("gethost... error");

	printf("Official name: %s \n", host->h_name);
	for(i=0; host->h_aliases[i]; i++)
		printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
	printf("Address type: %s \n", 
		(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");
	for(i=0; host->h_addr_list[i]; i++)
		printf("IP addr %d: %s \n", i+1,
					inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));

	WSACleanup();
	return 0;
}

void ErrorHandling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

gethostbyaddr_win

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
void ErrorHandling(char *message);

int main(int argc, char *argv[])
{
	WSADATA wsaData;
	int i;
	struct hostent *host;
	SOCKADDR_IN addr;
	if(argc!=2) {
		printf("Usage : %s <IP>\n", argv[0]);
		exit(1);
	}
	if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
		ErrorHandling("WSAStartup() error!"); 

	memset(&addr, 0, sizeof(addr));
	addr.sin_addr.s_addr=inet_addr(argv[1]);
	host=gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
	if(!host)
		ErrorHandling("gethost... error");

	printf("Official name: %s \n", host->h_name);
	for(i=0; host->h_aliases[i]; i++)
		printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
	printf("Address type: %s \n", 
		(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");
	for(i=0; host->h_addr_list[i]; i++)
		printf("IP addr %d: %s \n", i+1,
					inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));	
	WSACleanup();
	return 0;
}

void ErrorHandling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

本文章使用limfx的vscode插件快速发布