Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从浏览器一次网络请求流程中找寻优化手段 #43

Open
adodo0829 opened this issue Apr 28, 2020 · 0 comments
Open

从浏览器一次网络请求流程中找寻优化手段 #43

adodo0829 opened this issue Apr 28, 2020 · 0 comments

Comments

@adodo0829
Copy link
Owner

浏览器下的一次网络请求流程

以输入 http://github.com/appleguardu为例,一次 web 请求本质上就是一台主机去另一台主机获取对应的资源.

1.进程切换

当导航栏输入 http://github.com/appleguardu/后, 浏览器进程把这个url交给网络进程处理, 给我去这台主机下取资源;

2.解析请求的URL,DNS查询IP,准备端口

  • URL: 统一资源定位符
protocol: 协议头, 譬如有http,ftp等
host: 主机域名或IP地址
port: 端口号
path: 目录路径
query: 即查询参数
fragment: 即#后的hash值,一般用来定位到某个位置
  • DNS(域名与IP的映射系统)
由于不同主机之间都是通过IP地址来确定的,所以首先会进行域名解析,查询; 
浏览器会向域名服务器去查询http://github.com对应的Ip.

//  Ip查询也有一套优化机制: 缓存机制(可以减少网络请求)
浏览器缓存:   浏览器会按照一定的频率缓存 DNS 记录;
操作系统缓存: 如果浏览器缓存中找不到需要的 DNS 记录,那就去操作系统中找;
路由缓存:     路由器也有 DNS 缓存;
ISP(互联网服务提供商)的DNS服务器: ISP 有专门的 DNS 服务器应对 DNS 查询请求;
根服务器: ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行递归查询;(DNS 服务器先问根域名服务器.com 域名服务器的 IP 地址,然后再问.github 域名服务器,依次类推)
  • 端口port
    如果不是特别指定,http协议默认的80端口

3.等待 TCP 队列(浏览器对tcp的限制)

chorome浏览器有个机制, 统一域名下同时最多只能建立 6 个 TCP连接(浏览器的限制可能不一样), 如果超过 6 个请求, 那么剩下的会进入排队等待的状态, 等待前面的完事后自己再去建立连接

4.建立 TCP 连接

TCP 协议

TCP运行: 连接创建、数据传送和连接终止

  • 连接创建: 三次握手(保证可靠传输)
1.客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分;
客户端把这段连接的序号设定为随机数A。

2.服务器端应当为一个合法的SYN回送一个SYN/ACK。
ACK的确认码应为A+1,SYN/ACK包本身又有一个随机产生的序号B。

3.最后,客户端再发送一个ACK。此时包的序号被设定为A+1,而ACK的确认码则为B+1。
当服务端收到这个ACK的时候,就完成了三次握手,并进入了连接创建状态。

client侧: SYN         ACK
server侧:     SYN+ACK
  • 数据传输(一些机制)
使用序号,对收到的TCP报文段进行排序以及检测重复的数据;
使用校验和检测报文段的错误,即无错传输;
使用确认和计时器来检测和纠正丢包或延时;
流量控制(Flow control);拥塞控制(Congestion control);丢失包的重传。
  • 连接终止(四次挥手)
连接终止使用了四路握手过程(或称四次握手,four-way handshake),在这个过程中连接的每一侧都独立地被终止。
当一个端点要停止它这一侧的连接,就向对侧发送FIN,对侧回复ACK表示确认。
因此,拆掉一侧的连接过程需要一对FIN和ACK,分别由两侧端点发出。

client侧: FIN          ACK
server侧:     ACK  FIN

5.发送 HTTP 请求

当 TCP 连接建立之后, 浏览器与服务器之间开始通信,传输数据

构建请求报文

  • 请求行
GET /appleguardu HTTP/1.1
// 请求方式:(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE
// 资源路径URI: /appleguardu
// 协议版本: HTTP 1.1
  • 请求头(常用的)
Accept: 接收类型,表示浏览器支持的MIME类型(对标服务端返回的Content-Type)
Accept-Encoding: 浏览器支持的压缩类型,如gzip等,超出类型不能接收
Content-Type: 客户端发送出去实体内容的类型
Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache
If-Modified-Since: 对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中
Expires: 缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间
Max-age: 代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中
If-None-Match: 对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中
Cookie: 有cookie并且同域访问时会自动带上
Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive
Host: 请求的服务器URL
Origin: 最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私
Referer: 该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)
User-Agent: 用户客户端的一些必要信息,如UA头部等
  • 请求体
post 请求中参数常放在请求体中
如参数的序列化形式(a=1&b=2这种;
或者直接放表单对象(Form Data对象,上传时可以夹杂参数以及文件的等

注意: 如果请求的是缓存资源,则会终止请求

浏览器会对请求的文件资源进行检查, 如果这个请求的资源存在于浏览器缓存当中, 请求就会被拦截, 浏览器直接从本地缓存中获取后发给网络进程, 并结束此处请求过程
关于http缓存介绍

缓存分类

缓存可以简单的划分成两种类型: 强缓存(200 from cache)与协商缓存(服务端返回304);
强缓存: 浏览器如果判断本地缓存未过期,就直接使用,无需发起http请求;
协商缓存304: 当浏览器再次向服务端发起http请求,然后服务端(304)告诉浏览器文件未改变,让浏览器使用本地缓存

  • 如何区分两类缓存? 通过设置 http 头部信息
# 几种头部缓存属性
强缓存(http1.1): Cache-Control/Max-Age 
强缓存(http1.0): Pragma/Expires 

协商缓存(http1.1): If-None-Match/E-tag
协商缓存(http1.0): If-Modified-Since/Last-Modified

<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
HTML页面中也有一个meta标签可以控制缓存方案-Pragma,不过一般都是服务端设置
  • http1.0下的缓存控制
Pragma: 严格来说,它不属于专门的缓存控制头部,但是它设置no-cache时可以让本地强缓存失效(
属于编译控制,来实现特定的指令,主要是因为兼容http1.0,所以以前又被大量应用)

Expires: 服务端配置的,属于强缓存,用来控制在规定的时间之前,浏览器不会发出请求,而是直接使用本地缓存,
注意: Expires一般对应服务器端时间,如Expires: Fri, 30 Oct 1998 14:19:41
如果客户端时间和服务端不同步,可能造成浏览器本地的缓存无用或者一直无法过期

If-Modified-Since/Last-Modified: 这两个是成对出现的,属于协商缓存的内容;
其中浏览器的头部是If-Modified-Since,而服务端的是Last-Modified;
它的作用是,在发起请求时,如果If-Modified-Since和Last-Modified匹配,
那么代表服务器资源并未改变,因此服务端不会返回资源实体,而是只返回头部,通知浏览器可以使用本地缓存...
Last-Modified,顾名思义,指的是文件最后的修改时间,而且只能精确到1s以内
注意: 如果服务端的文件会周期性的改变, 导致缓存失效
  • http1.1下的缓存控制
Cache-Control: 缓存控制头部,有no-cache、max-age等多种取值

Max-Age: 服务端配置的,用来控制强缓存,在规定的时间之内,浏览器无需发出请求,直接使用本地缓存,
注意,Max-Age是Cache-Control头部的值,不是独立的头部,
譬如Cache-Control: max-age=3600,而且它值得是绝对时间,由浏览器自己计算

If-None-Match/E-tag: 这两个是成对出现的,属于协商缓存的内容;
其中浏览器的头部是If-None-Match,而服务端的是E-tag,
同样,发出请求后,如果If-None-Match和E-tag匹配,则代表内容未变,通知浏览器使用本地缓存;
和Last-Modified不同,E-tag更精确,它是类似于指纹一样的东西,所以会优先级也更高...
基于FileEtag INode Mtime Size生成,也就是说,只要文件变,指纹就会变,而且没有1s精确度的限制。

6.服务器处理 http 请求

当服务器接收到浏览器的请求报文后, 会交给对应的处理程序进行处理(此处省略...),然后想服务器返回响应报文

响应报文

  • 响应行
HTTP/1.1 200 OK
// 协议版本 状态码 状态码信息
// 200——表明该请求被成功地完成,所请求的资源发送回客户端
// 304——自从上次请求后,请求的网页未修改过,请客户端使用本地缓存
// 400——客户端请求有错(譬如可以是安全模块拦截)
// 401——请求未经授权
// 403——禁止访问(譬如可以是未登录时禁止)
// 404——资源未找到
// 500——服务器内部错误
// 503——服务不可用
  • 响应头(常用的)
Access-Control-Allow-Headers: 服务器端允许的请求Headers
Access-Control-Allow-Methods: 服务器端允许的请求方法
Access-Control-Allow-Origin: 服务器端允许的请求Origin头部(譬如为*)
Content-Type: 服务端返回的实体内容的类型
Date: 数据从服务器发送的时间
Cache-Control: 告诉浏览器或其他客户,什么环境可以安全的缓存文档
Last-Modified: 请求资源的最后修改时间
Expires: 应该在什么时候认为文档已经过期,从而不再缓存它
Max-age: 客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效
ETag: 请求变量的实体标签的当前值
Set-Cookie: 设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端
Keep-Alive: 如果客户端有keep-alive,服务端也会有响应(如timeout=38)
Server: 服务器的一些相关信息
  • 响应体
响应体一般是服务端需要传给客户端的内容;
如接口请求时: 实体中就是对应的信息的 json格式对象
如页面请求时: 实体中就是对应的 html 字符串

注意

一般来说, 请求头部和响应头部是互相匹配的
如请求头部的Accept要和响应头部的Content-Type匹配,否则会报错;
跨域请求时,请求头部的Origin要匹配响应头部的Access-Control-Allow-Origin,否则会报跨域错误
还有在使用缓存时,
请求头部的If-Modified-Since、If-None-Match分别和
响应头部的Last-Modified、ETag相对应

以上基本都是通过服务端程序来控制的

重定向

当响应状态码是 301 时,会发生重定向, 浏览器进程重新让导航进行工作, 修改 location 字段中的地址,继续上述的请求流程

8.浏览器解析并渲染页面

浏览器进程开始调度, 渲染进程通过管道接受网络进程的html数据,开始解析html工作

渲染进程工作流

  • 1.DOM Tree 构建
当渲染进程接收到导航的确认信息,开始接受HTML数据时,主线程会解析文本字符串为 DOM;
这里依靠 HTMl 解析器: 
接受字节流 -> 维护 token 栈 -> 生成节点node -> 组成 DOM;

遇到内嵌 script 时, DOM解析工作停止; js引擎介入执行(可能会修改dom结构);
执行完 js 后恢复解析工作, 所以 js 会阻塞 dom 解析.

遇到其他内联资源时(css,img)会通知网络进程去下载, 特别是 css;
js 在操作dom 样式时会依赖cssom,生成 layoutTree也需要 cssom; 
所以 css 又会阻塞 js 的执行
  • 2.样式计算, 构建cssom(css规则树)
这里会基于 CSS 选择器解析 CSS 获取每一个节点的最终的计算样式值;
对应的就是styleSheets
  • 3.计算布局, 生成layout tree
想要渲染一个完整的页面,除了获知每个节点的具体样式,还需要获知每一个节点在页面上的位置,
布局其实是找到所有元素的几何关系的过程。

这里通过遍历 DOM 及相关元素的计算样式,主线程会构建出包含每个元素的坐标信息及盒子大小的布局树。
布局树和 DOM 树类似,但是其中只包含页面可见的元素,如果一个元素设置了 `display:none` ,
这个元素不会出现在布局树上,伪元素虽然在 DOM 树上不可见,但是在布局树上是可见的。
  • 4.分层,绘制(layer -> paint)
为特定的节点生成专用图层(will-change属性), 生成 图层树;
为图层生成绘制表(记录了绘制指令和顺序), 提交到合成线程
  • 5.分块,光栅化
合成线程将图层分为图块, 通过光栅化生成位图(GPU 进程)
  • 6.合成,显示
图块被光栅化后会生成一个绘制命令, 通过 IPC 提交给浏览器进程去执行,
绘制到内存中然后展示在显示器上

9.断开TCP连接

当数据传送完毕,需要断开 tcp 连接,此时发起 tcp 四次挥手; 参见 TCP 协议;
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。
不过如果浏览器或者服务器在其头信息中加入了

Connection: Keep-Alive (http/1.1下默认启用, http/1.0默认close)
// keep-alive不会永远保持,它有一个持续时间,一般在服务器中配置(如apache,
另外长连接需要客户端和服务器都支持时才有效

TCP 连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个 TCP 连接 发送请求。
保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。 比如,一个 Web 页面中内嵌的图片就都来自同一个 Web 站点,如果初始化了一个持久连 接,你就可以复用该连接,以请求其他资源,而不需要重新再建立新的 TCP 连接。

参考

TCP 协议百科
HTTP headers

  • http2.0 了解
- 多路复用(即一个tcp/ip连接可以请求多个资源)
- 首部压缩(http头部压缩,减少体积)
- 二进制分帧(在应用层跟传送层之间增加了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)
- 服务器端推送(服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端)
- 请求优先级(如果流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定需要多少资源来处理该请求。)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant