title: 输入url发生了什么
categories: 计算机网络
tag:

  • 计算机网络
    date: 2021-12-19 10:05:34

输入 url 发生了什么?

这个问题属于老生常谈的经典问题了 下面给出面试简单版作答

  1. 浏览器地址栏输入 URL 并回车
  2. 浏览器查找当前 URL 是否存在缓存,并比较缓存是否过期(强缓存,协商缓存)
  3. DNS 解析 URL 对应的 IP
  4. 根据 IP 建立 TCP 连接(三次握手)
  5. 发送 http 请求
  6. 服务器处理请求,浏览器接受 HTTP 响应
  7. 浏览器解析并渲染页面
  8. 关闭 TCP 连接(四次挥手)

HTTP 缓存

缓存规则分为强制缓存协商缓存

强制缓存

当缓存数据库中有客户端需要的数据,客户端直接将数据从其中拿出来使用(如果数据未失效),当缓存服务器没有需要的数据时,客户端才会向服务端请求。

协商缓存

又称对比缓存。客户端会先从缓存数据库拿到一个缓存的标识,然后向服务端验证标识是否失效,如果没有失效服务端会返回 304,这样客户端可以直接去缓存数据库拿出数据,如果失效,服务端会返回新的数据

5_输入url发生了什么 - 图1

DNS 解析

DNS 地址解析

DNS 解析实际上就是寻找你所需要的资源的过程。假设你输入www.baidu.com,而这个网址并不是百度的真实地址,互联网中每一台机器都有唯一标识的 IP 地址,这个才是关键,但是它不好记,乱七八糟一串数字谁记得住啊,所以就需要一个网址和 IP 地址的转换,也就是 DNS 解析。

DNS 具体解析过程在之前的文章有===》链接

DNS 缓存

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

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

DNS 负载均衡

访问baidu.com的时候,每次响应的并非是同一个服务器(IP 地址不同),一般大公司都有成百上千台服务器来支撑访问,假设只有一个服务器,那它的性能和存储量要多大才能支撑这样大量的访问呢?DNS 可以返回一个合适的机器的 IP 给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,这种过程就是 DNS 负载均衡

建立 TCP 连接

TCP 提供一种可靠的传输,这个过程涉及到三次握手,四次挥手,下面我们详细看看 TCP 提供一种面向连接的,可靠的字节流服务。 其首部的数据格式如下

5_输入url发生了什么 - 图2

三次握手

第一次握手

客户端主动发送 SYN 包(Seq=x)到服务器,并进入 SYN_SEND 状态,等待服务器确认

第二次握手

服务器收到 SYN 包,必须确认客户的 SYN(ack=x+1),同时自己也发送一个 SYN 包(Seq=y),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;

第三次握手

客户端收到服务器的 SYN + ACK 包,向服务器发送确认包 ACK(ack=y+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

5_输入url发生了什么 - 图3

为什么是三次握手,不是二次或者 4 次握手

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

具体例子:“已失效的连接请求报文段”的产生在这样一种情况下:client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。”

为什么不是 4 次?

三次是保证双方互相明确对方能收能发的最低值。

理论上讲不论握手多少次都不能确认一条信道是“可靠”的,但通过 3 次握手可以至少确认它是“可用”的,再往上加握手次数不过是提高“它是可用的”这个结论的可信程度。

四次挥手

当客户端和服务器通过三次握手建立了 TCP 连接以后,当数据传送完毕,肯定是要断开 TCP 连接的啊。那对于 TCP 的断开连接,这里就有了神秘的“四次分手”。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,假设客户端主动关闭服务器被动关闭

5_输入url发生了什么 - 图4

第一次分手: 主机 1(可以使客户端,也可以是服务器端),设置 Sequence Number,向主机 2 发送一个 FIN 报文段;此时,主机 1 进入 FIN_WAIT_1 状态;这表示主机 1 没有数据要发送给主机 2 了;

第二次分手: 主机 2 收到了主机 1 发送的 FIN 报文段,向主机 1 回一个 ACK 报文段,Acknowledgment Number 为 Sequence Number 加 1;主机 1 进入 FIN_WAIT_2 状态;主机 2 告诉主机 1,我“同意”你的关闭请求;

第三次分手: 主机 2 向主机 1 发送 FIN 报文段,请求关闭连接,同时主机 2 进入 LAST_ACK 状态;

第四次分手: 主机 1 收到主机 2 发送的 FIN 报文段,向主机 2 发送 ACK 报文段,然后主机 1 进入 TIME_WAIT 状态;主机 2 收到主机 1 的 ACK 报文段以后,就关闭连接;此时,主机 1 等待 2MSL 后依然没有收到回复,则证明 Server 端已正常关闭,那好,主机 1 也可以关闭连接了。

为什么要四次分手?

TCP 协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP 是全双工模式,这就意味着,当主机 1 发出 FIN 报文段时,只是表示主机 1 已经没有数据要发送了,主机 1 告诉主机 2,它的数据已经全部发送完毕了;但是,这个时候主机 1 还是可以接受来自主机 2 的数据;当主机 2 返回 ACK 报文段时,表示它已经知道主机 1 没有数据发送了,但是主机 2 还是可以发送数据到主机 1 的;当主机 2 也发送了 FIN 报文段时,这个时候就表示主机 2 也没有数据要发送了,就会告诉主机 1,我也没有数据要发送了,之后彼此就会愉快的中断这次 TCP 连接。

为什么客户端最后还要等待 2MSL?

MSL(Maximum Segment Lifetime),TCP 允许不同的实现可以设置不同的 MSL 值。

第一,保证客户端发送的最后一个 ACK 报文能够到达服务器,因为这个 ACK 报文可能丢失,站在服务器的角度看来,我已经发送了 FIN+ACK 报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个 2MSL 时间段内收到这个重传的报文,接着给出回应报文,并且会重启 2MSL 计时器。

第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个 2MSL 时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

为什么建立连接是三次握手,关闭连接确是四次挥手呢?

建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。 而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送,从而导致多了一次。

TCP 流量控制

如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。

利用滑动窗口机制可以很方便地在 TCP 连接上实现对发送方的流量控制。

5_输入url发生了什么 - 图5

从图中可以看出,B 进行了三次流量控制。第一次把窗口减少到 rwnd = 300 ,第二次又减到了 rwnd = 100 ,最后减到 rwnd = 0 ,即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机 B 重新发出一个新的窗口值为止。B 向 A 发送的三个报文段都设置了 ACK = 1 ,只有在 ACK=1 时确认号字段才有意义。

TCP 为每一个连接设有一个持续计时器(persistence timer)。只要 TCP 连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口控测报文段(携 1 字节的数据),那么收到这个报文段的一方就重新设置持续计时器。

发送 HTTP 请求

首先,HTTP 的端口是 80/8080,而 HTTPS 的端口是 443

发送 HTTP 请求的过程就是构建 HTTP 请求报文并通过 TCP 协议中发送到服务器指定端口 请求报文由请求行请求报头请求正文组成。

请求行

请求行的格式为Method Request-URL HTTP-Version CRLF eg:

  1. GET index.html HTTP/1.1

常用的方法有: GET,POST, PUT, DELETE, OPTIONS, HEAD

其中 get 和 post 的区别

GET 在浏览器回退时是无害的,而 POST 会再次提交请求。

GET 产生的 URL 地址可以被 Bookmark,而 POST 不可以。

GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。

GET 请求只能进行 url 编码,而 POST 支持多种编码方式。

GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。

GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有。

对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制。

GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。

GET 参数通过 URL 传递,POST 放在 Request body 中。

GET会产生一个TCP数据包,而POST会产生两个TCP数据包。(这是浏览器决定的,复杂请求就会发送两个数据包)

当浏览器进行复杂请求时,会先发送一次 option 请求,查看该请求是否被允许,当 option 请求匹配正常后,会发送你的 post 请求。

详细的说就是:

  • 对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据);
  • 而对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)。

注意一点,并不是所有的浏览器都会发送两次数据包,Firefox 就发送一次

请求报头

5_输入url发生了什么 - 图6

图中可以看出,请求报头中使用了 Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie 等字段。Accept 用于指定客户端用于接受哪些类型的信息,Accept-Encoding 与 Accept 类似,它用于指定接受的编码方式。

请求正文

当使用 POST, PUT 等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的 Web 应用通常采用 Rest 架构,请求的数据格式一般为 json。这时就需要设置Content-Type: application/json

服务器处理请求并返回 HTTP 报文

它会对 TCP 连接进行处理,对 HTTP 协议进行解析,并按照报文格式进一步封装成 HTTP Request 对象,供上层使用。这一部分工作一般是由 Web 服务器去进行,HTTP 报文也分成三份,状态码响应报头响应报文

状态码

状态码详解见==>链接

响应报头

5_输入url发生了什么 - 图7

响应报文

你从服务器请求的 HTML,CSS,JS 文件就放在这里面

浏览器解析并渲染页面

5_输入url发生了什么 - 图8

就是Webkit解析渲染页面的过程。

  • 解析 HTML 形成 DOM 树
  • 解析 CSS 形成 CSSOM 树
  • 合并 DOM 树和 CSSOM 树形成渲染树
  • 浏览器开始渲染并绘制页面

这个过程涉及两个比较重要的概念回流重绘

  • DOM 结点都是以盒模型形式存在,需要浏览器去计算位置和宽度等,这个过程就是回流。

  • 等到页面的宽高,大小,颜色等属性确定下来后,浏览器开始绘制内容,这个过程叫做重绘。
    浏览器刚打开页面一定要经过这两个过程的,但是这个过程非常非常非常消耗性能,所以我们应该尽量减少页面的回流和重绘。

性能优化之回流重绘

回流

当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

会导致回流的操作:

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的 DOM 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法

一些常用且会导致回流的属性和方法:

  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • scrollIntoView()、scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

重绘

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

优化

CSS 方面

  • 避免使用 table 布局。
  • 尽可能在 DOM 树的最末端改变 class。
  • 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
  • 避免使用 CSS 表达式(例如:calc())。

JavaScript 方面

  • 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。

  • 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。

  • 也可以先为元素设置 display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。

  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。

  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

参考

  1. 链接:https://juejin.cn/post/7004638318843412493
  2. 掘金虚竹子