最简单的浏览器请求过程如下:
客户端地址栏中输入内容
,浏览器从地址栏中输入的内容获得服务器的IP地址和端口号
。浏览器用TCP的三次握手和四次回收与服务器建立连接
,建立连接以后浏览器向服务器发送拼好的报文,服务器收到拼接好的报文以后,也会给浏览器返回拼接好的报文内容,浏览器接受服务器返回的响应报文
,浏览器解析好报文渲染
到页面。
上述前提是我们在浏览器中输入的是IP地址,但是大多数情况下我们在浏览器中输入的是域名,输入域名的具体步骤如下:
第一步:用户输入
当用户在浏览器的地址栏里面输入内容时,浏览器会对输入的内容进行判断,是搜索内容
还是请求的URL
。
- 如果是搜索内容,浏览器会使用默认的搜索引擎合成带有搜索关键字的URL
- 如果是符合规则的URL,浏览器会根据规则给内容加上URL协议,从而生成完整的URL
第二步:DNS查询阶段
浏览器是如何知道我们输入的域名对应的IP地址是什么呢?这就依靠我们的DNS
这个大功臣。DNS主要是通过一系列的域名解析服务器
,试图把域名转换成对于的TCP/IP协议中的IP地址。
DNS服务器特性:高可用、高并发、分布式。
DNS服务器作用:因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
DNS查询模式有两种:递归查询
、迭代查询
- 一般客户机和服务器之间的查询方式属于递归查询。
- DNS服务器之间的查询方式属于迭代查询。
DNS(Domain Name System)是“域名系统”的英文缩写。
DNS的域名空间结构:
(图片来源:极客时间趣谈网络协议)
客户机和服务器之间的查询方式属于递归查询。
解析流程:
0)客户端输入URL后,首先在本地计算机的缓存中查找。如果在本地无法获得查询信息,则将查询请求发给DNS服务器。本地计算机的缓存中查找查找步骤如下
1)电脑客户端会发出一个DNS请求比如https://juejin.cn/
,并且发给本地的域名服务器(本地DNS)。本地DNS一般分为两种,第一,通过DHCP配置的本地DNS由你的网络服务商(ISP)提供。第二,电信、移动等设备自动分配的,通常在你的网络服务商的某个机房。
2)本地DNS收到客户端的请求,本地DNS这台服务器缓存了一张域名和IP地址映射的数据库。如果能找到https://juejin.cn/
会直接返回IP地址,否则会去问根DNS。
3)根DNS是最高层次的不直接用于域名解析,根DNS收到本地DNS的请求发现是.cn,会将下一级域名服务器地址给本地DNS
4)本地DNS转向下一级域名服务器,如此递归直到找到目标域名的DNS服务器。最后将对应的IP地址告诉本地DNS。
5)本地DNS服务器再将对应的IP地址告诉客户端。
6)客户端和目标地址建立连接。
思考:
我们在进行DNS域名解析的过程中,DNS的解析可能会给出CDN服务器的地址而不是目标服务器的实际地址,因为CDN会缓存网站的大部分资源,减轻源服务器的压力。所以有的HTTP请求就不需要发到源服务器,DNS服务器就可以给我们返回数据了。
但是通过后台服务器动态生成的页面属于动态资源,CDN服务器没办法缓存的,HTTP请求只能从源服务器获取。
源服务器(也就是目标服务器)外表表现的就是一个IP地址,针对高并发,目标网站一般是多个服务器组成的比较稳定的集群。
第三步:建立TCP连接
当完成域名到IP地址的解析,地址栏中输入的域名也就相当于在地址栏中输入了IP地址,浏览器用TCP的三次握手与服务器建连接。
如果请求是HTTPS还需要建立TLS连接。
问题:
-
为什么要三次,而不是两次呢?
答:因为可靠,比如A要发起一个连接,可能发出去请求包就丢了,可能超时了,A无法确认结果就还会再发,所以B收到请求以后,B知道A要与他进行连接,但是B愿意不愿意连接必须也要告诉A,但是B告诉A的话也是消息发出去,但是不知道能否达到A,可能A已经挂了,所以A也需要向B发送应答之应答,此刻A出去的消息虽然也是有去无回,但是在这么下去就没有结束了。只要保证双方的消息都有去有回就行了。当然三次握手不仅仅是为了双方建立连接,还要沟通TCP的序号问题。
-
开始的时候,请求方和接收方都是close状态。服务端主动监听某个端口,处于listen状态。
-
客户端发送请求连接报文,连接SYN,之后处于SYN_sent状态。
-
服务端收到请求后,返回SYN,之后处于SYN_rcvd状态。
-
客户端收到服务器发送的SYN和ACK之后,发送SYN和ACK,之后处于established状态。
-
服务端收到YN和ACK以后,处于established状态。
至此,客户端和服务端都做到了一发一收成功了。建立连接至此已经成功了。
目前大多数建立的TCP连接都是默认开启 Keep-Alive的,这样建立好的TCP连接可以再多次请求中复用,而不需要一次又一次的握手重新建立连接。
其实就是浏览器访问服务端之后, 一个http请求的底层是tcp连接, tcp连接要经过三次握手之后,开始传输数据, 而且因为http设置了keep-alive,所以单次http请求完成之后这条tcp连接并不会断开, 而是可以让下一次http请求直接使用.当然keep-alive肯定也有timeout, 超时关闭.
第四步:发送请求
建立连接后,浏览器就要发送HTTP的请求。浏览器构建数据包(包含请求行,请求头,请求正文,并把该域名相关Cookie等数据附加到请求头),然后向服务器发送请求消息。 请求分为三部分,
- 第一部是
起始行
,描述请求或者响应的基本信息 - 第二部分是
头部字段集合
, - 第三部分才是真正的
请求实体
,数据的传输。
第一部分和第二部分经常被合称为请求头
,响应头
。
浏览器发送get请求的时候,HTTP报文经常是只有header没有body。HTTP协议对header的大小没有限制,头部太大会影响网络的运行效率,所以web浏览器不允许过大的请求头。
1、请求行(起始行)
GET / HTTP/1.1
这就是一个简单的请求行,他描述的是客户端想要如何操作服务端的资源。
浏览器请求方法是GET,请求目标是服务器的根目录,告诉服务器我用的协议版本好是1.1,服务器也需要用1.1版本回复客户端。
2、状态行(响应行)
HTTP/1.1 200 OK
这是服务端返回给浏览器的状态行,告诉浏览器报文使用的协议版本号是1.1,状态码是200,一切OK。
HTTP/1.1 404 Not Found
,告诉浏览器报文使用的协议版本号是1.1,状态码是404,表示服务器接受到了请求,但是没有找到对应的资源。
3、头部字段
请求头和响应头的头部字段结构基本相同。唯一区别就是起始行不同。头部字段的格式都是以key-value的形式,中间用冒号隔开,最后用换行符换行。HTTP头部字段比较灵活,可以扩展。除了可以使用标准的已有头外,我们也可以自定义头部字段。
第五步:接受响应
服务器接收到消息后根据请求信息构建响应数据(包括响应行,响应头,响应正文),然后发送回网络进程。 网络进程接收到响应数据后进行解析。 如果发现响应行的返回的状态码为301,302,说明服务器要我们去找别人要数据,找谁呢?找响应头中的Location字段要,Location的内容是需要重定向的地址url。获取到这个url一切重新来过。 如果返回的状态码为200,说明服务器返回了数据。
第六步:渲染页面
浏览器进程发出“提交文档”(文档是响应体数据)消息给渲染进程,渲染进程接收到消息后会和网络进程建立传输数据的通道,网络进程将“文档”传输给渲染进程。
1、构建DOM树
浏览器无法直接理解HTML、CSS、JS。所以需要将HTML转换成浏览器能够理解的结构-DOM树。
2、样式计算
我们构建好DOM树以后,DOM节点的样式还不知道,这时候我们就需要进行样式计算了。
2.1 把CSS转换成浏览器能够理解的结构
当渲染引擎接收到CSS文本时,会执行转换操作,把CSS文本转换成浏览器可以理解的结构-styleSheets。
2.2 转换样式表中的属性值,使其标准化
比如转换em为px、red转换成rab(255,0,0),blod解析为700等等计算渲染引擎容易理解的,标准化的计算值。
2.3 计算出DOM树中每个节点的具体样式
这里会涉及到CSS继承规则和层叠规则
3、布局阶段
3.1 创建布局树
为了构建布局树,浏览器大体上完成了下面这些工作:
- 遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
- 而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 body.p.span 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。
3.2 布局计算
现在我们有了布局树,接下来就会计算布局节点的坐标信息了。
总结:在 HTML 页面内容被提交给渲染引擎之后,渲染引擎首先将 HTML 解析为浏览器可以理解的 DOM;然后根据 CSS 样式表,计算出 DOM 树所有节点的样式;接着又计算每个元素的几何坐标位置,并将这些信息保存在布局树中。