本系列文章围绕着“往浏览器输入网址后发生了什么”介绍计算机网络的相关基础知识。本文将介绍往浏览器中输入一个网址后客户端如何封装http消息和发送dns请求查询目标主机的ip。
往浏览器输入网址后发生了什么?
1.客户端(如浏览器)解析url
url的组成部分如下所示:
客户端会按照 协议、域名、文件路径以及请求参数 对url进行解析(就是解析出以上信息)。
url如果没有以/结尾表示访问一个指定文件,以/结尾表示访问指定目录下的默认文件,例如: http://www.lab.glasscom.com/dir/ 访问的是/dir 目录下的index.html文件。
http://www.lab.glasscom.com/dir 访问的是 /dir文件。但是如果不存在 /dir文件,则会把它当做是目录处理,因而会访问/dir/下的默认文件
2. 生成http请求消息
解析完url后,客户端会根据请求的类型(这里以GET和POST为例)生成http请求消息。http请求消息和响应消息如下所示:
PS:get请求没有消息体,post才有。
消息头就是我们所说的header字段,包含诸如Content-Type、Cookie、Host等内容。
请求行的URI包括文件路径和请求参数。
请求消息和响应消息的不同仅在第一行
一个请求消息中只能写一个URI。
如下为get和post请求报文格式:
客户端会根据上述(a)的格式生成http消息。
* 头信息列举:
通用头
请求头
响应头
响应消息中的状态码概述
3. 向DNS服务器查询域名对应IP
客户端生成http消息后会委托操作系统将消息发送给web服务器。操作系统拿到http报文后为了将其发送给web服务器,会先根据域名查询目标主机的IP地址。
* 简单了解IP相关知识
在网络中,所有设备都会被分配一个IP地址,IP地址相当于是服务器的坐标,客户端得知这个坐标才能将消息发送到服务器主机。
实际的IP是一串32比特的数字(IPv4标准),并按8比特分为4组,每组用原点隔开,每组以十进制表示。除了IPv4标准外还有IPv6标准,IPv6下的IP地址则是128比特的长度用于支持更多可能的地址,这里只介绍IPv4下的内容。
IP地址分为网络号和主机号两部分,网络号代表主机所在的子网网络,子网与子网间通过路由器和集线器连接和通信,主机号代表某台主机在子网中的具体位置。
IP地址被分为A~E类,每类的网络号和主机号长度是不定的,例如C类IP的前3个字节是网络号,第4个字节是主机号;而B类IP地址前2个字节是网络号,后两个字节是主机号。
IP地址会用附加信息来表示哪部分是IP的网络号,哪部分是IP的主机号。这个附加信息就是子网掩码。子网掩码是一串与IP长度相同的32比特数字,也分为主机号和网络号,其中左边一半全为1,右边一半全为0,全为1的部分为网络号,全为0的部分为主机号。
例如: 10.11.12.13/255.255.255.0 中 255.255.255.0就是子网掩码,它的前3字节是1,第四字节为0,因此表示10.11.12.13中前3字节是网络号,第4字节是主机号。
此外还可以用 网络号比特数 表示子网掩码,如 24。24表示前三字节为网络号(8*3),16表示前两个字节为网络号(8*2),8表示只有第一个字节是网络号。
域名也代表某一服务器的地址,但域名的使用是为了方便人类的记忆,而IP才是服务器所在主机的真正位置。
回到正题。
从域名解析为IP就涉及到DNS(域名解析)
为了得到域名对应的IP,客户端(浏览器)在将http请求委托给操作系统发送前会查询浏览器的DNS缓存找域名对应IP,如果命中缓存则客户端会将IP带给操作系统,没有命中则操作系统会查找本机的DNS缓存,如果还是没有命中,操作系统就会通过DNS解析器向DNS服务器发起DNS请求。
* 关于DNS解析器
DNS解析器本质上是操作系统socket库中的一段程序,用于向dns服务器发送查询请求并获取域名对应的IP并返回给客户端。而socket库是用于调用网络功能的通用程序组件。
客户端通过DNS解析器发起dns请求的过程如下所示
客户端(浏览器)委托操作系统调用socket库的gethostbyname()这个系统调用函数,此时用户程序(浏览器)的工作会暂停并切换到操作系统的dns解析器运行,即用户态切换到内核态。
DNS解析器根据浏览器提供的域名生成DNS查询消息(DNS查询报文)并交给操作系统中的TCP/IP协议栈,由协议栈通过网卡以UDP传输的方式发送给DNS服务器。在这里,操作系统必须得知道DNS服务器的IP,这样操作系统才该把报文发送给那个DNS服务器,而这个DNS服务器的IP是已经事先设置好的,如下所示
而这个设置好的DNS服务器一般是距离客户端主机所在城市内不远处的一个DNS服务器,我们称之为本地DNS服务器。
发送DNS查询报文后,DNS服务器会返回对应域名的IP地址给协议栈(至于DNS服务器根据域名返回对应ip的过程会在之后介绍),并按原路返回给DNS解析器,解析器将IP写入到浏览器指定的内存地址中(从内核态切回用户态),这样当该浏览器待会再想知道这个域名的IP时就直接从用户程序内存中取即可,无需在做DNS查询。此外,操作系统和浏览器还会对这个域名与IP的映射关系做缓存,即使关闭了浏览器下次再打开并请求也无需做DNS查询请求。除非缓存时间过期。
得到IP的值的浏览器终于可以开始发送HTTP请求消息。
4. DNS报文在多个DNS服务器间的传递
当客户主机的操作系统将DNS查询报文发送给本地DNS服务器到DNS服务器返回IP给客户端主机这段期间发生了什么?这里需要介绍一下DNS查询报文的内容和DNS服务器。
* DNS查询报文和回答报文
DNS 查询和回答报文的格式是一致的,如下所示:
报文头部的12个字节包含了一些控制信息如,报文是查询还是回答报文,回答报文中告诉客户端它访问的是否为权威DNS服务器,客户端是否希望DNS服务器提供递归查询,以及该DNS服务器是否支持递归查询,回答的RR数等。
查询报文的实体部分需要包括2个部分:
要查询的域名
记录类型
记录类型表示域名对应何种类型的记录(即告诉DNS服务器,根据传过来的域名,它应该返回什么类型的响应内容)。
dns服务器中保存着多条资源记录(RR),资源记录不只包括域名与IP的映射,但包括域名与其他信息的映射,而资源记录中保存的到底是域名与什么信息的映射取决于记录类型。
如类型是A表示该条记录是域名对应的IP地址;
类型为MX,是该域名对应的邮箱服务器域名;
类型为CNAME,该记录是一个别名域名对应的真正域名;
类型为NS时该记录存着要查询的域名是由哪个DNS服务器解析的(也就是记录着一个目标DNS服务器的域名),此时这个DNS服务器应该还保存着目标DNS服务器的域名与IP的映射。如下所示:
baidu.com IN NS ns1.domain.com
baidu.com IN NS ns2.domain.com
ns1.domain.com IN A 111.222.111.111
ns2.domain.com IN A 123.111.222.111
除了上述类型,还有其他的记录类型。感兴趣的朋友可以自行查阅。
回答报文会包含多条资源记录(RR),每条资源记录包括4个字段:(Name, Value, Type, TTL)。分别是域名,域名对应的值(如IP、权威DNS域名、邮箱域名等),记录类型,记录的有效时间。
* 域名层级
一个域名的层级从左到右依次提升,每个层级用.分开。以www.abc.com为例,com是顶级域名,com域的下一层是abc域,再下一层是www这个名字。
* DNS服务器
DNS服务器本质是一个存储着域名与相应信息映射关系的具有层级的、分布式数据库集群。
** 存储着域名与相应信息映射关系
DNS服务器里面保存的数据我们称之为资源记录,如下所示:
当带有 域名、class和记录类型 的请求报文到达DNS服务器后,它会根据这3个信息从资源记录中找到这3个字段都匹配的记录返回给客户端。相当于 select `响应数据` from 表 where `域名`="xxx" and `class`="xxx" and `记录类型`="xxx";
** 具有层级的、分布式数据库集群
分布式:无论从服务器负载、数据备份和故障时高可用的角度来说,所有的域名信息不可能保存到一台DNS服务器上,而是由多台分布到世界上多台DNS服务器共同维护。
具有层级:DNS服务器是具有层级的,其层级是根据域名的层级来划分,由上到下分为 根域名DNS服务器、顶级域名DNS服务器和权威DNS服务器。
全世界的根域DNS服务器大概有十几台。
每一台根域名服务器都知道所有顶级域名DNS服务器的域名和IP,而且不管哪一个本地域名服务器要对某个域名进行解析而自己无法解析都会首先求助于根域名服务器。
每一层DNS服务器都保存着下一层DNS服务器的所有IP地址,并且所有层级的每一台DNS服务器会保存所有根域的DNS服务器IP。这么一来就能做到在本地DNS服务器能找到根域DNS服务器IP,再从根据服务器逐层往下找到其他DNS服务器。
当某一台权威DNS服务器不能给出最后的查询回答,就会告诉请求者下一步应该找到哪个权威DNS服务器。
用户主机向本地域名服务器的查询都是采用递归查询(所以用户主机只用发出一次DNS请求即可得到想要的IP地址,其他的请求交给本地DNS服务器和其他DNS服务器即可),本地域名服务器向根域名服务器的查询通常采用迭代查询,但也可以采用递归查询。
如下图所示:
有了上面的扩展知识后,我们再来说DNS报文发送到本地DNS服务器后发生了什么:
以www.lab.glasscom.com为例
· DNS查询报文到达本地DNS服务器后,如果本地DNS服务器没有记录目标域名对应的IP,则会将查询转发给根DNS服务器;
· 根域DNS服务器也没有该域名的资源记录,但判断出其顶级域为com,所以会把com域的DNS服务器的IP返回给本地DNS服务器;
· 本地DNS服务器再根据这个IP向com域的DNS服务器发起查询请求,但com域的DNS服务器也没有www.lab.glasscom.com的资源记录,但判断出该域名的权威域名为glasscom.com,找到了glasscom.com域的DNS服务器的IP地址并返回。
· 本地DNS服务器向glasscom.com域的DNS服务器发起DNS查询请求,查到了www.lab.glasscom.com的IP
· 最终本地DNS服务器将目标IP返回给客户端主机,同时本地DSN服务器也会对该域名的IP映射进行缓存。下一次被访问到相同的域名时直接查询DNS缓存而不再向根域DNS服务器发起请求
上面这种DNS查询方式是迭代查询,每次都是由本地DNS服务器发起请求;
此外还有其他查询方式如递归查询,以及直接查询DNS缓存。
下面我们看看递归查询和迭代查询的区别:
递归查询
由本机查询本地DNS服务器,本地DNS服务器如果没有命中目标域名则向根服务器请求,根服务器根据顶级域名找到对应的顶级域名服务器的IP并向该顶级域名服务器发送请求,同理顶级域名服务器再向对应的权威域名服务器发送DNS查询请求的到目标地址IP,并将其原路返回给本机。
如下图展示了递归查询过程:
迭代查询
迭代查询就是上文中提到过的查询方式,本机请求本地域名服务器,本地域名服务器请求根域名服务器拿到顶级域名服务器的ip,本地域名服务器再去请求顶级域名服务器拿到权威域名服务器的ip,本地域名服务器再去请求权威域名服务器拿到目标ip,再返回给本机。
如下图