从输入URL回车之后发生了什么

4,633

从输入URL回车之后发生了什么

从输入URL到页面展示总体来说分为以下一个步骤:

  1. 输入地址
  2. 根据地址栏输入的地址发生DNS解析查询其IP。
  3. 通过IP地址与服务器建立TCP连接。
  4. 客户端向服务端发送HTTP请求,如果服务器返回以 301 之类的重定向,浏览器根据相应头中的 location 再次发送请求
  5. 服务器收到请求并响应HTTP请求,处理请求生成 html 代码,返回给浏览器。
  6. 浏览器开始解析渲染页面并显示
  7. 关闭连接

输入地址

当我们开始在浏览器中输入网站时,浏览器其实就已经在智能的匹配一些可能的url了,它会从书签、历史记录等地方找到已经输入的url字符串所对应的url,然后给出智能提示,让你可以补全url地址。对于google的chrome浏览器,它甚至会从缓存中把网页展示出来;换言之当我们输入地址时,页面就出来了。

DNS解析

DNS(Domain Name System)服务是和HTTP协议一样位于应用层的协议。它提供域名到IP地址之间的解析。用户通常使用主机名或域名来访问对方的计算机,而不是通过IP地址访问。

DNS解析的过程就是寻找哪台机器上有你需要资源的过程。当你在浏览器中输入一个地址时,例如www.baidu.com,其实不是百度网站真正意义上的地址。互联网上每一台计算机的唯一标识是它的IP地址,但是IP地址并不方便记忆。用户更喜欢用方便记忆的网址去寻找互联网上的其它计算机,也就是上面提到的百度的网址。所以互联网设计者需要在用户的方便性与可用性方面做一个权衡,这个权衡就是一个网址到IP地址的转换,这个过程就是DNS解析。它实际上充当了一个翻译的角色,实现了网址到IP地址的转换。

什么是DNS

域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。 --维基百科

域名解析的过程是逐级查询的

  1. 浏览器缓存: 首先会向浏览器的缓存中读取上一次访问的记录,在chrome可以通过地址栏中输入chrome://net-internals/#dns查看缓存的当前状态
  2. 操作系统缓存: 查找存储在系统运行内存中的缓存。在mac中可以通过下面的命令清除系统中的DNS缓存。
dscacheutil -flushcache
  1. 在host文件中查找:如果在缓存中都查找不到的情况下,就会读取系统中预设的host文件中的设置。
  2. 路由器缓存:有些路由器也有DNS缓存的功能,访问过的域名会存在路由器上。
  3. ISP DNS缓存:互联网服务提供商(如中国电信)也会提供DNS服务,比如比较著名的 114.114.114.114,在本地查找不到的情况下,就会向ISP进行查询,ISP会在当前服务器的缓存内查找是否有记录,如果有,则返回这个IP,若没有,则会开始向根域名服务器请求查询。
  4. 顶级DNS服务器/根DNS服务器:根域名收到请求后,会判别这个域名(.com)是授权给哪台服务器管理,并返回这个顶级DNS服务器的IP。请求者收到这台顶级DNS的服务器IP后,会向该服务器发起查询,如果该服务器无法解析,该服务器就会返回下一级的DNS服务器IP(nicefilm.com),本机继续查找,直到服务器找到(www.nicefilm.com)的主机。

我们可以通过dig命令查看域名解析的记录

dig math.stackexchange.com

我们重点看返回的应答,会看到有四条记录,返回了该网址的四个IP

;; ANSWER SECTION:
math.stackexchange.com.	34	IN	A	151.101.129.69
math.stackexchange.com.	34	IN	A	151.101.65.69
math.stackexchange.com.	34	IN	A	151.101.1.69
math.stackexchange.com.	34	IN	A	151.101.193.69

34是TTL的值,表示该域名的缓存时间,即该时间内不用重新查询。A是该DNS查询的记录类型,表示返回一个IPv4格式的地址。还有其他记录类型诸如 NS(返回查询的服务器地址)、AAAA(返回IPV6格式的地址)、CNAME(域名的别名)等。

解析过程

DNS解析是一个递归查询的过程。

DNS递归查询

上述图片是查找www.google.com的IP地址过程。首先在本地域名服务器中查询IP地址,如果没有找到的情况下,本地域名服务器会向根域名服务器发送一个请求,如果根域名服务器也不存在该域名时,本地域名会向com顶级域名服务器发送一个请求,依次类推下去。直到最后本地域名服务器得到google的IP地址并把它缓存到本地,供下次查询使用。从上述过程中,可以看出网址的解析是一个从右向左的过程: com -> google.com -> www.google.com。但是你是否发现少了点什么,根域名服务器的解析过程呢?事实上,真正的网址是www.google.com.,并不是我多打了一个.,这个.对应的就是根域名服务器,默认情况下所有的网址的最后一位都是.,既然是默认情况下,为了方便用户,通常都会省略,浏览器在请求DNS的时候会自动加上,所有网址真正的解析过程为: . -> .com -> google.com. -> www.google.com.。

DNS优化

了解了DNS的过程,可以为我们带来哪些?上文中请求到google的IP地址时,经历了8个步骤,这个过程中存在多个请求(同时存在UDP和TCP请求,为什么有两种请求方式,请自行查找)。如果每次都经过这么多步骤,是否太耗时间?如何减少该过程的步骤呢?那就是DNS缓存。

DNS缓存

DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。

  • 在你的chrome浏览器中输入:chrome://dns/,你可以看到chrome浏览器的DNS缓存。
  • 系统缓存主要存在/etc/hosts(Linux系统)中:

DNS负载均衡

不知道大家有没有思考过一个问题: DNS返回的IP地址是否每次都一样?如果每次都一样是否说明你请求的资源都位于同一台机器上面,那么这台机器需要多高的性能和储存才能满足亿万请求呢?其实真实的互联网世界背后存在成千上百台服务器,大型的网站甚至更多。但是在用户的眼中,它需要的只是处理他的请求,哪台机器处理请求并不重要。DNS可以返回一个合适的机器的IP给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,这种过程就是DNS负载均衡,又叫做DNS重定向。大家耳熟能详的CDN(Content Delivery Network)就是利用DNS的重定向技术,DNS服务器会返回一个跟用户最接近的点的IP地址给用户,CDN节点的服务器负责响应用户的请求,提供所需的内容。

TCP连接

TCP 是一种面向有连接的传输层协议。 它可以保证两端(发送端和接收端)通信主机之间的通信可达。 它能够处理在传输过程中丢包、传输顺序乱掉等异常情况;此外它还能有效利用宽带,缓解网络拥堵。

拿到了要请求的资源服务器IP后,浏览器通过操作OS的socket与服务器进行TCP连接(一般来说操作系统已经封装好了TCP/IP等协议,提供套接字给应用去使用,该部分涉及到标准网络模型的知识,另外再开篇拓展。)

这个连接就是我们所熟知的三次握手 本机主动打开连接

  • 第一次,本机将标识位 SYN 置为 1, seq = x(Sequence number)发送给服务端。此时本机状态为SYN-SENT
  • 第二次,服务器收到包之后,将状态切换为SYN-RECEIVED,并将标识位 SYN 和 ACK都置为1, seq = y, ack = x + 1, 并发送给客户端。
  • 第三次,客户端收到包后,将状态切换为ESTABLISHED,并将标识位ACK置为1,seq = x + 1, ack = y + 1, 并发送给服务端。服务端收到包之后,也将状态切换为ESTABLISHED。

需要注意的一点是,有一些文章对ACK标识位 和 ack(Acknowledgement Number)的解释比较模糊,有一些画图的时候干脆就写在一起了。虽然这两者有关联,但不是同一个东西,搞清楚这个误区可以更方便去理解。还有一些会把第二次握手描述成两个包(比如某百科……),实际上这也是不正确的

  • 标识位ACK置为1 表示我已确认收到seq为x的包,并回复确认序号ack = x + 1
  • 而SYN表示这是我第一次随机生成seq的序列x,此后我每次发送的包都会在上一次发送的基础上增加y(有数据的时候,y是数据的长度,没有的时候y = 1)。所以,当seq已初始化完成之后,没必要再把SYN置为1 理解了这两点,也就不难理解为什么三次握手分别是SYN、ACK/SYN、ACK了。

标识位(TCP FLAG) TCP的头部固定有20个字节,其中分配了6bits给TCP FLAG,组合起来用来表示当前包的类型。分别是 URGACKPSHRSTSYNFIN(CWRECE放在保留位,暂不考虑)

  • URG:紧急指针,用于将要发送的包标识为“紧急”,这意味着不必等待前段数据被响应处理完即可发送给接收端。
  • ACK:确认标识,用于表示对数据包的成功接收。
  • PSH:推送标识,表示这个数据包应该被立即发送,不需要等待额外的数据。
  • RST:reset标识,用来异常关闭连接。
  • SYN:同步标识,表示TCP连接已初始化。
  • FIN:完成标识,用于拆除上一个SYN标识。一个完整的TCP连接过程一定会有SYN 和 FIN包。 至此我们了解了一个TCP 连接的过程,通道通了,是时候利用这个通道送东西了。 我们从传输层再回到应用层。

发送HTTP请求

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议[1]。HTTP是万维网的数据通信的基础。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。

在应用层,浏览器会分析这个url,并设置好请求报文发出。请求报文中包括请求行、请求头、空行、请求主体。https默认请求端口443, http默认80。

  • 请求行:请求行中包括请求的方法,路径和协议版本。
  • 请求头:请求头中包含了请求的一些附加的信息,一般是以键值的形式成对存在,比如设置请求文件的类型accept-type,以及服务器对缓存的设置。
  • 空行:协议中规定请求头和请求主体间必须用一个空行隔开
  • 请求主体:对于post请求,所需要的参数都不会放在url中,这时候就需要一个载体了,这个载体就是请求主题。

服务端响应HTTP请求

在接收和解释请求消息后,服务器返回一个HTTP响应消息。 HTTP 响应由三个部分组成,分别是:状态行、消息报头、响应正文。 状态代码:由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:

  • 1xx:指示信息--表示请求已接收,继续处理
  • 2xx:成功--表示请求已被成功接收、理解、接受
  • 3xx:重定向--要完成请求必须进行更进一步的操作
  • 4xx:客户端错误--请求有语法错误或请求无法实现
  • 5xx:服务器端错误--服务器未能实现合法的请求

常见状态代码、状态描述、说明:

  • 200 OK :客户端请求成功
  • 400 Bad Request :客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorized :请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403 Forbidden :服务器收到请求,但是拒绝提供服务
  • 404 Not Found :请求资源不存在,eg:输入了错误的URL
  • 500 Internal Server Error :服务器发生不可预期的错误
  • 503 Server Unavailable :服务器当前不能处理客户端的请求,一段时间后可能恢复正常

HTTP缓存

HTTP 消息报头包括:普通报头、请求报头、响应报头、实体报头。具体不作介绍。 响应正文:就是服务器返回的资源的内容

http缓存 请求是浏览器的一个优化点,我们可以通过缓存来减少不必要的请求,进而加快页面的呈现。通过简单地设置http头部可以使用缓存的功能。一般来说有三种设置的方式

Last-Modify(响应头) + If-Modified-Since(请求头) 服务器在返回资源的时候设置Last-Modify当前资源最后一次修改的时间,浏览器会把这个时间保存下来,在下次请求的时候,请求头部If-Modified-Since 会包含这个时间,服务端收到请求后,会比对资源最后更新的时间是否在If-Modified-Since设置的时间之后,如果不是,返回304状态码,浏览器将从缓存中获取资源。反之返回200和资源内容。

ETag(响应头) + If-None-Match(请求头) 根据资源标识符来确定文件是否存在修改,服务器每一次返回资源,都会在Etag中存放资源的标识符,浏览器收到这个标识符,在下一次请求的时候将标识符放在If-None-Match中,服务端将判断是否匹配,如果不匹配,返回200以及新的资源,反之返回304,浏览器从缓存中获取资源

Cache-Control/Expires(响应头) 首先这不是一种方法,而是协议更替中的一种演化。 在http 1.0的时代,我们基于Pragma 和 Expires 控制缓存的生命周期。我们可以通过设置Pragma为no-cache关闭缓存功能,同样也可以在Expires中设置一个缓存失效的时间。需要注意的是,这个失效的时间是相对于服务器的实践而言的,如果人为地改变了客户端的时间,是会导致缓存失效的。

所以,为了解决这个问题,HTTP1.1的协议加入了Cache-Control,通过设置Cache-Control的max-age可以控制缓存的周期。在这个周期内,资源是新鲜的,浏览器再一次需要使用资源的时候,就不会发出请求了。

渲染页面

在浏览器没有完整接受全部 HTML 文档时,它就已经开始显示这个页面了,不同浏览器可能解析的过程不太一样,这里我们只介绍 WebKit 的渲染过程。

  1. 解析HTML,构建 DOM 树
  2. 解析 CSS ,生成 CSS 规则树
  3. 合并 DOM 树和 CSS 规则,生成 render 树
  4. 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
  5. 绘制 render 树( paint ),绘制页面像素信息
  6. 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上

需要注意的是,这是一个渐进的过程,呈现引擎为了力求显示的及时,会在文档请求不完全的情况下就开始渲染页面,同时,如果在解析的过程中遇到script的时候,文档的解析将会停止下来,立即解析执行脚本,如果脚本是外部的,则会等待请求完成并解析执行。所以,为了不阻塞页面地呈现,一般会把script脚本放在文档的最后。

在最新的HTML4和HTML5规范中,也可以将脚本标注为defer,这样就不会停止文档解析,而是等到解析结束后才执行。HTML5 增加了一个选项,可将脚本标记为async,以便由其他线程解析和执行。

连接关闭

现在的页面为了优化请求的耗时,默认都会开启持久连接(keep-alive),那么一个TCP连接确切关闭的时机,是这个tab标签页关闭的时候。这个关闭的过程就是著名的四次挥手。关闭是一个全双工的过程,发包的顺序的不一定的。一般来说是客户端主动发起的关闭,过程如下。

假如最后一次客户端发出的数据seq = x, ack = y;

  • 客户端发送一个FIN置为1的包,ack = y, seq = x + 1,此时客户端的状态为 FIN_WAIT_1
  • 服务端收到包后,状态切换为CLOSE_WAIT发送一个ACK为1的包, ack = x + 2。客户端收到包之后状态切换为FNI_WAIT_2
  • 服务端处理完任务后,向客户端发送一个 FIN包,seq = y; 同时将自己的状态置为LAST_ACK
  • 客户端收到包后状态切换为TIME_WAIT,并向服务端发送ACK包,ack = y + 1,等待2MSL后关闭连接。 为什么客户端等待2MSL? MSL: 全程Maximum Segment Lifetime,中文可以翻译为报文最大生存时间。 等待是为了保证连接的可靠性,确保服务端收到ACK包,如果服务端没有收到这个ACK包,将会重发FIN包给客户端,而这个时间刚好是服务端等待超时重发的时间 + FIN的传输时间。