在浏览器地址栏输入url到按下回车发生了什么?

3,065

这个问题是一个非常普遍且经典的问题,一个合格的web程序员必须要搞懂的问题!

详解

解析url

浏览器通过地址栏捕获到url地址之后,首先对url地址进行解析。url的解析如下图所示:

一个完整的url,包含上述几部分,协议部分一般都是 http或者https。域名部分可以是 一段域名例如:baidu.com 也可以是 ip地址,域名最后也会被解析为ip地址。该ip地址的作用就是在互联网中确定服务器的位置,紧接着是端口后,端口号确定的是在服务器中运行的具体的程序。路径部分表示的是在该程序中资源的具体标识,查询参数作用主要是为了发送数据。

DNS解析

一般域名都需要从运营商购买,因为ip地址不方便记忆,域名比较好记。域名都会和ip地址绑定,那么,在这里就需要做DNS解析,所谓DNS解析其实就是,根据域名找到其绑定的 ip地址。

查找的顺序如下图:

DNS的查找过程解析:

  1. 浏览器会先检查是否存在缓存,因为如果访问过一次该域名的话,会把结果缓存在浏览器中。
  2. 操作系统也会有自己的DNS缓存,但在这之前,会检查域名是否存在于本地的Hosts文件中。
  3. 路由器中也会有自己的缓存。
  4. IPS DNS缓存 就是在客户端电脑上设置的首选DNS服务器。
  5. 在前边所有的情况下都没有找到缓存的情况下,会连接互联网,把请求转发到互联网的根域。

下图展示了DNS的一个查询过程

TCP连接

确定好目标服务器的ip地址和端口号后,就开始和远程服务器建立TCP链接。三次我说的过程如下:

  • 发送方:发送消息,等待...
  • 接收方:接收消息,并给发送方回信,此时发送方接收到消息后,从发送方的角度就可明白自己的消息是可以发过去的。但是接收方还不能确定自己的消息是否可以正常发送。
  • 发送方:接收到发送方的回信后,在给接收方发一个消息,此时从接收方的角度就能明白自己的消息可以可以正常发送。

画成图如下:

TCP三次握手的好处在于的好处在于,发送方可以确认接收方仍然在线

发送http请求

http协议是建立在tcp/ip协议之上的,tcp保证连接通畅,http就可以正常的进行请求和响应了。首先http请求是一个无状态的请求,且只能由浏览器主动发起,服务器进行响应。

浏览器发送请求的报文会携带以下信息

  • 请求路径
  • 查询参数
  • 请求方法
  • 请求头
  • 请求体

服务器接收请求

在服务端会监听浏览器端发送的http请求,当浏览器的请求发出后,服务端就会接受该请求,并解析出相应的信息,选择对应的逻辑进行处理(比如:查找对应的静态页面,保存文件,操作数据库,转发....),并将处理的结果响应给浏览器端。

node服务器的一个简单例子如下:

const http = require('http'); // 引入http模块

const server = http.createServer((req, res) => {
	// req保存了浏览器携带的信息
    // 解析出req的相关信息,比如 路径 请求方法 请求头 请求体等
    // 编写服务端逻辑处理代码
    // 响应
    res.end();
})

server.listen(3000, () => console.log('::3000'));

// 假设该程序巡行在 ip地址为 140.143.201.230的服务器上
// 假设 140.143.201.230 绑定的域名是 www.aabbcc.com
// 那么请求地址 可以为   http://www.aabbcc.com:3000/home

// 当浏览器请求 http://www.aabbcc.com:3000/home之后,会DNS解析到服务器的ip地址为140.143.201.230,那么上述http.createServer接收的回调函数就会执行。

服务器响应

服务器执行完逻辑之后,需要给浏览器响应内容(无论是要从服务器获取数据,还是在服务器做了什么操作,都需要给浏览器一个响应)

响应一般包含以下几部分

  • 状态码
  • 状态文本
  • 响应头
  • 响应体

服务器响应的同时肯定会伴随着浏览器端接收,等浏览器端彻底接收完毕之后,TCP就要进行4次挥手,并断开连接了。

TCP链接断开

TCP链接的断开需要经过"四次挥手",那么就需要一方主动的释放另外一方被动的释放。大体的过程如下:

  1. 浏览器端发消息通知服务器现在需要断开(第一次挥手)
  2. 服务器接到要断开的请求之后,给浏览器返回消息,告诉浏览器我正在准备释放(第二次挥手)
  3. 此时浏览器接到消息后正在等待服务器释放完成,而服务器正在准备释放的过程
  4. 当服务器释放完成后,再通知浏览器我已经释放完成了。(第三次挥手)
  5. 浏览器接收到服务器释放完成的消息后,再给服务器发送消息告诉服务器我已经知道你释放完成了,服务器收到消息后,就能确认自己释放完成的消息已经通知到了(第四次挥手)

浏览器解析资源

当浏览器接收到服务器响应的资源后,会对资源进行解析。

  1. 首先,查看Response Header,根据响应头的指示做不同的事情,比如重定向,存储cookie,解压gzip,缓存资源等等。
  2. 接下来获取MIME类型(查看响应头的 Content-Type的值),根据不同的资源类型采用不同的解析方式

渲染页面

一般来说从地址栏输入地址后,绝大多是情况下响应的都是 html文件,那么就说以说页面是如何渲染html页面的,html页面中一般也会嵌入css,js,图片等资源。因此如果解析到这些资源的时候,会再次向目标服务器发起请求,那么又会经历从解析url地址开始的各个步骤。

整个页面的加载如下图所示:

html页面的加载

首先要知道浏览器解析是从上往下一行一行地解析的。

  1. 解码:传输回来的其实都是一些二进制字节数据,浏览器需要根据文件指定编码(例如UTF-8)转换成字符串,也就是HTML 代码
  2. 预解析:预解析做的事情是提前加载资源,减少处理时间,它会识别一些会请求资源的属性,比如img标签的src属性,并将这个请求加到请求队列中。
  3. 符号化:符号化是词法分析的过程,将输入解析成符号,HTML 符号包括,开始标签、结束标签、属性名和属性值。它通过一个状态机去识别符号的状态,比如遇到<,>状态都会产生变化。
  4. 构建树:在上一步符号化中,解析器获得这些标记,然后以合适的方法创建DOM对象并把这些符号插入到DOM对象中。

浏览器的容错机制:你从来没有在浏览器看过类似”语法无效”的错误,这是因为浏览器去纠正错误的语法,然后继续工作。

css解析

一旦浏览器下载了 CSS,CSS 解析器就会处理它遇到的任何 CSS,根据语法规范解析出所有的 CSS 并进行标记化,然后我们得到一个规则表。 在匹配一个节点对应的 CSS 规则时,是按照从右到左的顺序的,例如:div p { font-size :14px }会先寻找所有的p标签然后判断它的父元素是否为div

所以我们写 CSS 时,尽量用 id 和 class,千万不要过度层叠。

javaScript编译执行

大致流程如下:

主要是三个阶段

  1. 词法分析:js脚本加载完毕后,会首先进入语法分析阶段,它首先会分析代码块的语法是否正确,不正确则抛出“语法错误”,停止执行。
  2. 预编译:js有三种运行环境分别是 全局环境,函数环境,eval。每进入一个不同的运行环境都会创建一个对应的执行上下文,根据不同的上下文环境,形成一个函数调用栈,栈底永远是全局执行上下文,栈顶则永远是当前执行上下文。
  3. 执行: js虽然是单线程的,但是实际参与工作的线程一共有四个:JS引擎线程(主),事件触发线程,定时器触发线程,HTTP异步请求线程

总结

从浏览地地址栏输入地址按下回车,可以看做是一次请求的发起,那么必然会经历以下几个步骤:

  1. 解析url地址
  2. DNS解析
  3. TCP链接
  4. 发送http请求
  5. 服务器接收请求
  6. 服务器响应
  7. TCP链接断开
  8. 浏览器解析资源

那么我们需要明白,浏览器可以发送请求的方式不止地址栏输入地址这一种。比如a标签,img,link,script,form表单,ajax,fetch等等,这些方式都可以发出http请求,那么他们都会经历上述的过程,最终拿到资源,只是拿到资源的类型不一样,那么浏览器的处理方式也不一样。