TCP/IP 网络编程(七)---域名及网络地址

92 阅读5分钟

域名和域名系统

(一)域名

域名(Domain Name) 是互联网上用于识别和定位计算机、服务器或其他网络资源的名称。是人们用来访问网站或其他服务的地址。例如 example.com 是一个域名。

(二)域名系统

域名系统(Domain Name System,DNS) 是一个分布式的数据库系统,它负责对 IP 地址和域名进行相互转换。例如,当你在浏览器中输入 example.com 时,DNS 将这个域名解析成相应的IP地址,从而帮助你的浏览器找到并连接到 example.com 服务器。

通过域名和 IP 地址访问网站,这两者有什么区别呢?

从结果上看二者没有区别,都能访问同一个网站,但其接入过程不同。域名是赋予服务器端的虚拟地址,而非实际地址,所以要将虚拟地址转化为实际地址,这就是 DNS 的作用。浏览器通过默认 DNS 服务器获取该域名对应的 IP 地址信息,之后才真正接入该网站。

(三)DNS 服务器解析域名 IP 地址的应答过程

计算机内置的默认 DNS 服务器并不知道网络上所有域名的 IP 地址信息。若该 DNS 服务器无法解析,则会询问其他 DNS 服务器,并提供给用户:

image.png

IP 地址和域名之间的转换

一般情况下,IP 地址比域名发生变更的概率要高,所以利用 IP 地址编写程序并非上策。一旦注册域名可能永久不变,因此利用域名编写程序会好一些。

(一)利用域名获取 IP 地址

(1)gethostbyname 函数

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

向此函数传递域名字符串,就会返回域名对应的 IP 地址。返回的地址信息装入 hostent 结构体,此结构体定义如下:

struct hostent 
{
    char  *h_name;       // 主机的正式名称
    char  **h_aliases;   // 主机的别名列表
    int   h_addrtype;    // 地址类型,通常是 AF_INET(IPv4)或 AF_INET6(IPv6)
    int   h_length;      // 地址长度(以字节为单位)
    char  **h_addr_list; // 主机的地址列表(IPv4 地址)
};
  • h_name:主机的正式名称。例如,"www.example.com"

  • h_aliases:主机的别名数组,包含主机的其他名称。这是一个以 NULL 结尾的字符串数组。

  • h_addrtype:地址类型,例如 AF_INET 表示 IPv4,AF_INET6 表示 IPv6。

  • h_length:主机地址的长度(字节数),对于 IPv4 是 4 字节,对于 IPv6 是 16 字节。

  • h_addr_list:主机地址列表,包含指向主机地址的指针数组。每个地址指针是一个以网络字节序表示的 IP 地址。这个数组也是以 NULL 结尾的。

image.png

(2)示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>


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

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("Adress type: %s \n", (host->h_addrtype == AF_INET) ? "AF_INET" : "AF_INET6");
    for(i = 0; host->h_addr_list[i]; i++)   // 获取所有IP
        printf("IP addr %d: %s \n", i + 1, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
    return 0;
}
image.png

(3)说明

示例中第 34 行使用了 inet_ntoa 函数进行类型转换,此函数将网络字节序整数型 IP 地址转换成字符串形式,进行转换的原因是 h_addr_list 指向主机地址的指针数组。每个地址指针是一个以网络字节序表示的 IP 地址:

image.png

(4)基于 Windows 的实现

Windows 中有类似功能的同名函数:

#include <winsock2.h>
struct hostent* gethostbyname(const char* name);
#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);
}

(二)利用 IP 地址获取域名

(1)gethostbyaddr 函数

#include <netdb.h>
// 成功时返回 hostent 结构体变量地址值,失败时返回 NULL 指针
struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);
  • addr: 指向一个包含网络字节序 IP 地址的缓冲区的指针。这个缓冲区应该包含实际的地址数据。

  • len: 地址的长度。对于 IPv4 地址,长度通常是 4 字节;对于 IPv6 地址,长度通常是 16 字节。

  • family: 地址的地址族,通常是 AF_INET(IPv4)或 AF_INET6(IPv6)。

(2)示例

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

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

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

    memset(&addr, 0, sizeof(addrgcc));
    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(int 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(int 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;
}
image.png

(3)基于 Windows 的实现

#include <winsock2.h>
struct hostent* gethostbyaddr(const char* addr, int len, int type);
#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);
}