目录
4、调用gethostbyname系统接口将域名解析成IP地址
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html 当我们使用域名访问远端的服务器时,软件内部会先将域名转换成IP地址,然后通过IP地址去连接远端的服务器。这个将域名转换成IP地址的过程就叫做域名解析。操作系统中会将最近解析过的域名与IP地址缓存起来,称之为DNS缓存,有时可能需要清理DNS缓存,重新获取最新的IP地址。今天就给大家介绍一下如何进行域名解析及域名解析的完整流程,以及如何刷新DNS缓存,以供大家借鉴或参考。
1、什么是域名?
域名是指互联网中的网站或者服务器的地址,这些名称由多个字符组成,通常是以".com"、".net"、".cn"等结尾,比如大家常用的www.baidu.com(百度)、www.163.com(网易)等。一个好的域名,是连接客户和企业的桥梁,可以增加企业的知名度,使得客户更加容易地找到它们的网站,提高客户的转化率。
2、为什么使用域名?
首先,域名便于记忆。由于IP地址不方便记忆且不能显示地址组织的名称和性质,人们设计出了域名,并通过域名系统(DNS,Domain Name System)来将域名和IP地址相互映射。域名好记忆很多,使人能更方便地访问互联网,而不用去记住能够被机器直接读取的IP地址数串。
其次,远端服务器的IP可能会变动。域名一旦确定下来,一般就不会频繁地更改,特别是百度和网易这种门户网站,用户习惯使用固定的域名去访问指定的网站。但域名对应的IP地址可能会变更,在IP地址变更后,用户还能正常地通过域名去访问(对域名进行解析获取最新的IP地址)。
此外,同一个域名可能会配置多个IP地址,不同的IP地址对应着不同的运营商线路,比如移动线路、电信线路和联通线路(不同的用户可能使用不同运营商的网络),从不同线路进来的,会解析出对应线路的IP地址,这样就避免了跨运营商网络访问,可以有效地提高访问的速度。
有的客户端软件支持用户去配置要访问的服务器地址,可以直接添加域名地址。有的软件则会内置要访问的服务器域名,不需要将服务器地址的配置暴露给用户(没有提供配置远端服务器地址的入口),软件在登录时直接使用内置的域名地址登录远端的服务器,比如QQ、微信及企业微信等。
3、域名解析的完整流程
当软件使用域名访问远端的服务器时,软件内部会先将域名转换成IP地址,然后通过IP地址去连接远端的服务器,然后再和远端的服务器进行数据交互。将域名转换成IP地址的过程,就称为域名解析。
**为啥域名要转换成IP地址后才能访问远端的服务器呢?**其实很好理解,软件是通过网络和远端的服务器进行交互的。从TCPIP的分层模型来看:
域名属于应用层的概念(解析域名所使用的DNS协议,也属于应用层的协议),而进入到网络层,则需要使用IP路由去寻找远端的服务器,IP路由肯定要使用到IP地址,所以应用层需要事先将域名转换成IP地址后,使用IP地址去连接远端的服务器。
在进行域名解析时,底层会使用专用的DNS域名查询协议去查询域名对应IP地址。一般我们不需要关注DNS查询协议具体的格式和交互过程,我们只需要了解查询的大概流程即可。下面我们就来详细介绍一下域名解析的完整流程。
DNS服务器大致分为三种类型:根DNS服务器、顶级域DNS服务器和权威DNS服务器,其中顶级域DNS服务器主要负责诸如com、org、net、edu、gov 等顶级域名,如下:
根DNS服务器存储了所有顶级域DNS服务器的 IP 地址,可以通过根服务器找到顶级域服务器,比如百度的域名www.baidu.com,根服务器会返回所有维护 com 这个顶级域服务器的 IP 地址。然后你任选其中一个顶级域服务器发送请求,该顶级域服务器拿到域名后能够给出负责当前域的权威服务器地址。以百度的域名为例,顶级域服务器将返回所有负责百度这个域的权威服务器地址。接着任选其中一个权威服务器地址查询"www.baidu.com"的具体 IP 地址,最终权威服务器会返回给你具体的 IP 地址。此外,本地 DNS 服务器是具有缓存功能的,通常两天内的记录都会被缓存。
所以,通过DNS系统查询域名对应的 IP 的具体步骤如下:
- ① 操作系统先查本地 hosts文件 中是否有记录,如果有,则直接返回相对应映射的IP地址。hosts文件位于C:\Windows\System32\drivers\etc路径中,内容如下:
- ② 如果本地hosts文件中没有配置,则主机向自己的本地DNS服务器发送查询报文,如果本地DNS服务器缓存中有,将直接返回结果。
- ③ 如果本地服务器缓存中没有(操作系统中会有DNS缓存),则从内置在内部的根DNS服务器列表(全球13台,固定的IP地址)中选一个发送查询报文。
- ④ 根服务器解析域名中的后缀名,告诉本地服务器负责该后缀名的所有顶级服务器列表。
- ⑤ 本地服务器选择其中一个顶级域服务器发送查询请求,顶级域服务器拿到域名后继续解析,返回对应域的所有权威服务器列表。
- ⑥ 本地服务器再向返回的权威服务器发送查询报文,最终会从某一个权威服务器上得到具体的 IP 地址。
- ⑦ 主机返回结果IP。
在这里,给大家重点推荐一下我的几个热门畅销专栏 :(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到430多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!)
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法 ,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力 !所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
**专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!**专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战经验为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对C++相关知识点进行详细地展开与剖析!专栏涉及了C/C++开发领域多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!
专栏3:
VC++常用功能开发汇总https://blog.csdn.net/chenlycly/article/details/124272585
专栏将10多年C++开发实践中常用的功能,以高质量的代码展现出来,并对相关功能的实现细节进行了详细的说明。这些常用的代码,其质量与稳定性是有保证的,可以直接拿过去使用,可以有效地解决C++软件开发过程中遇到的问题。
4、调用gethostbyname系统接口将域名解析成IP地址
当我们使用客户端软件或者浏览器通过域名访问远端的服务器时,作为用户,不感知域名解析的过程,如果当前使用的是浏览器,则由浏览器内部去负责进行域名解析;如果当前使用的是客户端软件,则由客户端软件通过代码去完成域名解析。
**那如何通过代码将域名解析成IP地址呢?**其实很简单,只要调用系统API函数gethostbyname即可实现。
注意,gethostbyname函数可能会产生短暂的堵塞,该函数内部会先到系统DNS缓存中去查找;如果没找到,则到网卡上配置的DNS服务器上去查询域名对应的IP地址;如果本地DNS查询不到,则会到远端的DNS服务器上去查询,所以这个域名解析的过程可能会比较耗时。至于DNS域名的详细查询过程,文章开始时已经详细讲述,此处就不再赘述了。
所以,我们需要将对gethostbyname函数调用的代码,放置在一个新的线程中,等解析出来后再将IP信息投递出来给主线程。相关的代码如下所示:
cpp
// 域名解析线程函数
UINT __stdcall QueryDomainThread( LPVOID pParam )
{
char* lpszDomainName = (char*)pParam;
struct hostent *pHost = gethostbyname( lpszDomainName );
if( NULL == pHost )
{
// ::PostMessage // 通知主线程域名解析失败
return 0;
}
if ( pHost->h_addr_list[0] != NULL )
{
u32 dwIP = (*(in_addr*)pHost->h_addr_list[0]).S_un.S_addr;
// ::PostMessage // 通知主线程域名解析成功,将解析出来的IP投递过去
}
return 0;
}
// 发起域名解析,创建新的线程去解析
LRESULT StartQueryDomain( char* lpszDomainName )
{
// 此处不能直接将局部变量lpszDomainName传到线程函数中,因为启动线程的_beginthreadex返回时,线程函数不一定跑起来了
// 所以最好搞一个成员变量或者全局变量,将lpszDomainName中的字符串拷贝下来,然后给_beginthreadex传递这个声明周期
// 更长的变量
strcpy( g_szMDomainName, lpszDomainName );
// 线程函数QueryDomainThread的实现,上面已经给出
HANDLE hThread= (HANDLE)_beginthreadex( NULL, 0, QueryDomainThread, (void*)g_szMDomainName, 0, NULL );
if( hThread != NULL )
{
CloseHandle( hThread );
return S_OK;
}
return S_FALSE;
}
5、为什么需要清理系统DNS缓存?
假设服务器的IP地址变更,但我们PC端的系统DNS缓存中没有更新(我们在实际项目中遇到过),根据上面讲到的域名解析的流程,我们PC客户端软件使用域名访问远端的服务器时,发现本端系统DNS缓存中存放有域名对应的IP地址,就会直接从缓存中取出IP地址,就不会再向DNS服务器请求IP地址了。
系统之所以要建立系统DNS缓存,目的是为了提高应用访问服务器的效率,进行域名解析时如果系统缓存中目标域名对应的IP记录,直接取出来使用即可,就不用每次都向域名服务器发起域名查询请求,有效地减少了域名解析的时间。但DNS缓存也有不好的影响,比如此处的问题。
但从系统DNS缓存中取出的是老的IP地址,服务器IP地址已经变更,所以也就无法访问到服务器了。
当遇到这类可以连外网但服务器无法访问时,可能是系统DNS缓存引起的,可以尝试清理一下系统DNS缓存,这样在解析域名时就会向DNS服务器发起域名查询请求,就能查询到最新的IP地址了,也就能正常地访问到服务器了。
6、使用cmd命令清理DNS缓存
可以直接在cmd命令行窗口中使用ipconfig /flushdns命令即可清理系统的DNS缓存,如下所示:
这个命令其实是调用系统程序ipconfig.exe,在程序启动时给程序传递命令行参数/flushdns,程序启动起来后检测到命令行参数/flushdns,就会去执行清理DNS缓存的操作。
我们还可以使用ipconfig /displaydns命令去查看系统DNS缓存中的信息,如下所示:
DNS缓存中存放着一条一条域名解析记录,每一条记录中的字段说明如下:
1)记录名称 :是您查询 DNS 的名称,并且记录(地址或其他内容)属于该名称。
2)记录类型 :是显示为数字的类型 - 尽管更常见的是通过名称来引用它们,但在内部(在 DNS 协议中)每个都有一个数字。类型 1 是"A",表示"地址",即 IPv4 地址。(IPv6 使用类型 28,"AAAA",作为地址的四倍。)"PTR",类型 12,是指向主机名的"指针"------最常用于将 IP 地址映射回其名称。"CNAME"是"规范名称"。
3)生存时间 :是缓存条目必须到期的时间(以秒为单位)。
4)数据长度 :似乎是以字节为单位的长度 - IPv4 地址为四个字节,IPv6 为十六个字节。对于 CNAME 或 PTR,Windows 显示一个静态数字(4 或 8,取决于您的系统)------这实际上是保存实际文本的内存地址的大小。
DNS 回复的"答案"部分是查询的实际答案,"附加"部分包含查找实际答案可能需要的信息。例如,粘合记录。
5)<type>记录:显示存储的实际值。
7、通过代码去清除系统DNS缓存
上面我们说了,在cmd中执行系统DNS缓存清理的命令其实就是给系统程序ipconfig.exe传递了/flushdns参数,ipconfig.exe程序内部应该是调用系统库中的接口去实现清理的。于是在系统目录中找到ipconfig.exe文件,然后拖入到Dependency Walker工具中,看看ipconfig.exe有没有引用系统DNS相关的库以及调用了哪个具体的接口。
拖入后看到ipconfig.exe引用了系统库DNSAPI.dll,并看到调用了该库中的DnsFlushResolverCache接口:
于是到微软MSDN上搜索DnsFlushResolverCache函数,但并没有搜索到,那基本可以确定该接口是没有公开的。
**对于这类非公开的API函数,那可以使用LoadLibrary显示加载,并用GetProcess得到函数指针,直接调用之即可。**通过搜索,得到该函数的原型声明:
cpp
BOOL WINAPI DnsFlushResolverCache(VOID);
所以,从DNSAPI.DLL动态库中显式加载调用DnsFlushResolverCache的代码如下所示:
cpp
BOOL __stdcall DnsFlushResolverCache()
{
BOOL bRet = FALSE;
typedef BOOL (WINAPI *PfuncDnsFlushResolverCache)(VOID);
HMODULE hDnsModule = LoadLibrary( _T("dnsapi.dll") );
if ( hDnsModule != NULL )
{
PfuncDnsFlushResolverCache pFlushFun = GetProcAddress( hDnsModule, "DnsFlushResolverCache" );
if ( pFlushFun != NULL )
{
pFlushFun();
bRet = TRUE;
}
FreeLibrary( hDnsModule );
}
return bRet;
}
这样我们在软件登录服务器失败时,可以尝试自动清理一下缓存,然后重新发起登录。