yuanyuanbyte / Blog

圆圆的博客,预计写七个系列:JavaScript深入系列、JavaScript专题系列、网络系列、Webpack系列、Vue系列、JavaScript基础系列、HTML&CSS应知应会系列。
306 stars 125 forks source link

浏览器系列之从 URL 输入到页面展现发生了什么,详解 TCP 三次握手和四次挥手 #120

Open yuanyuanbyte opened 2 years ago

yuanyuanbyte commented 2 years ago

本系列的主题是浏览器,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末

如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。

前言

很多大公司面试喜欢问这样一道面试题,输入URL到看见页面发生了什么?今天我们来总结一下。

总体来说分为以下几个过程:

浏览器缓存

浏览器会判断所请求的资源是否有对应 URL 的缓存,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。

根据是否需要重新向服务器发起请求来分类,浏览器缓存分为强缓存和协商缓存。

详细的浏览器缓存策略:304 状态码是什么意思?图解 HTTP 强缓存和协商缓存

域名解析(DNS)

在浏览器输入网址后,首先要经过域名解析,因为浏览器并不能直接通过域名找到对应的服务器,而是要通过 IP 地址。

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

浏览器如何通过域名去查询 URL 对应的 IP 呢

小结

浏览器通过向 DNS 服务器发送域名,DNS 服务器查询到与域名相对应的 IP 地址,然后返回给浏览器,浏览器再将 IP 地址打在协议上,同时请求参数也会在协议搭载,然后一并发送给对应的服务器。接下来介绍向服务器发送 HTTP 请求阶段,HTTP 请求分为三个部分:TCP 三次握手、http 请求响应信息、关闭 TCP 连接。

TCP 三次握手

在客户端发送数据之前会发起 TCP 三次握手用以同步客户端和服务端的序列号和确认号,并交换 TCP 窗口大小信息。

三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息

在这里插入图片描述

TCP 三次握手的过程

刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。 进行三次握手:

ESTABLISHED :表示TCP连接已经成功建立

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

为什么会采用三次握手,若采用两次握手可以吗? 四次呢?

“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。——谢希仁著《计算机网络》

PS:失效的连接请求:若客户端向服务端发送的连接请求丢失,客户端等待应答超时后就会再次发送连接请求,此时,上一个连接请求就是『失效的』。

若建立连接只需两次握手,客户端并没有太大的变化,仍然需要获得服务端的应答后才进入TCP连接成功状态,但是服务端在收到连接请求后就进入TCP连接成功状态。如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。服务端正确接收并确认应答,双方便建立了连接开始通信,通信结束后释放连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在 某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端 ,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,由于只有两次握手,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。

为什么不是四次握手呢? 大家应该知道通信中著名的蓝军红军约定, 这个例子说明, 通信不可能100%可靠, 而上面的三次握手已经做好了通信的准备工作, 再增加握手, 并不能显著提高可靠性, 而且也没有必要。

红军协同对抗蓝军问题

在网络协议中,有这样一个经典问题:红军协同对抗蓝军问题

处于两地的红军A与红军B要与蓝军作战,但单独的红军A或红军B打不过蓝军,而红军A与红军B联合对抗蓝军则100%取得胜利。

于是红军A与红军B需要商议在何时进攻,但由于无线网络信号质量很差,无法确保红A与红B发出的消息能够送达对方,在此情境下,能否设计出一种可靠的通信协议使得红军一定取得胜利(即通信信道不完全可靠的情况下,设计出完全可靠的通信协议)。

分析:

请求确认

假定红A计划与红B在次日凌晨2点共同向蓝军发起攻击,红A必定要向B发送请求进攻报文“次日2点进攻蓝军”,但是由于通信信道的不可靠性,红B必须向红A发送一个确认报文。 在这种协议下,对红A来说,是否发动攻击取决于有没有收到B的确认报文,而对于红B来说,是否发动攻击取决于有没有收到红A的请求进攻信号,如图所示:

在这里插入图片描述

而在该协议中红B并不知道红A有没有收到确认报文,假如红B的确认报文丢失,红A只能等待,而红B单独进攻蓝军,最后失败告终。

确认的确认

为了解决该问题,即需要让红B知道红A已收到确认报文,在原来协议的基础上增加:红A收到确认报文后向红B发送“确认的确认”。 在这种协议下,对红A来说,收到红B的确认报文后决定发起进攻,而对红B来说,在收到“确认的确认”报文后决定发起进攻。

在这里插入图片描述

但实际上“确认的确认”报文也可能丢失,而红A并不知道B是否收到了“确认的确认”,因此,如果“确认的确认”丢失,会导致红A单独作战。

……

为解决以上问题,需红B再次发送对“确认的确认”的确认报文,但这同样会导致相同的问题,无限循环下去。

总结

在不可靠通信信道上无法设计出一种完全可靠的通信协议,因为对最后一次确认报文的发送,发送方无法知晓接收方是否收到,因而发送方无法判定约定是否有效。

发送 HTTP 请求

TCP链接建立后发送HTTP请求

请求报文由请求行(request line)、请求头(header)、请求体四个部分组成,如下图所示:

在这里插入图片描述

请求行包含请求方法、URL、协议版本

POST  /chapter17/user.html HTTP/1.1

请求行:

在这里插入图片描述

服务器响应 HTTP 请求

服务器响应 HTTP 请求并返回 HTTP 报文。

服务器是网络环境中的高性能计算机,它侦听网络上的其他计算机(客户机)提交的服务请求,并提供相应的服务,比如网页服务、文件下载服务、邮件服务、视频服务。而客户端主要的功能是浏览网页、看视频、听音乐等等,两者截然不同。 每台服务器上都会安装处理请求的应用——web server。常见的 web server 产品有 apache、nginx、IIS 或 Lighttpd 等。

响应报文由响应行(response line)、响应头部(header)、响应主体三个部分组成。如下图所示:

在这里插入图片描述

HTTP/1.1 200 OK // 状态行

状态行:

在这里插入图片描述

根据请求类型的不同,响应的数据格式也有所不同,有可能是二进制文件流、JSON 对象、字符串、HTML 文件等。

浏览器解析渲染页面

浏览器拿到响应文本 HTML 后,接下来介绍下浏览器渲染机制

在这里插入图片描述

浏览器解析渲染页面分为一下五个步骤:

根据 HTML 解析 DOM 树

深度优先遍历,顾名思义即是先抓着一个元素一直往下遍历它的子孙元素,直到没有未被遍历的元素时则换下一个元素继续往下遍历。当全部元素都遍历完了就结束。

根据 CSS 解析生成 CSS 规则树

结合 DOM 树和 CSS 规则树,生成渲染树

根据渲染树计算每一个节点的信息(布局)

根据计算好的信息绘制页面

TCP 四次挥手断开连接

TCP 四次挥手

当数据传送完毕,需要断开 tcp 连接,此时发起 tcp 四次挥手。

建立一个连接需要三次握手,而终止一个连接要经过四次挥手。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端 在结束它的发送后 还能接收来自另一端数据的能力。

在这里插入图片描述

注:
FIN 表示关闭连接
ACK 表示确认

MSL是TCP报文里面最大生存时间,它是任何报文段被丢弃前在网络内的最长时间。

刚开始双方都处于 TCP连接成功状态,客户端或服务器均可主动发起挥手动作,假如是客户端先发起关闭请求。四次挥手的过程如下:

TCP 状态变迁图

一个连接从开始建立到断开,经历了一连串的状态变化。

这次主要分析下它的状态变迁图,粗的实线箭头表示正常的客户端状态变迁,粗的虚线箭头表示正常的服务器状态变迁:

在这里插入图片描述

将关闭部分的状态转移摘出来,得到下图:

在这里插入图片描述

TCP 的半关闭

牢记 TCP 是 全双工 的。

既然一个TCP连接是全双工(即数据在两个方向上能同时传递,可理解为两个方向相反的独立通道),因此每个方向必须单独地进行关闭。

全双工:意味着,TCP的收发是可以在两个方向上同时进行的。在任意时刻,通信双方既可以发送数据也可以接收数据,每个方向的数据流是独立的。

半关闭:TCP提供了连接的一端 在结束了它的发送后 还能接收来自另外一端数据的能力。但是只有很少的应用程序利用它。

为了实现这个特性,编程接口必须提供一种方法来说明“我已经完成了数据的传送,并且发了FIN给另外一端,但是我还是想接收另外一端发送来的数据,直到结束(向我发送FIN)”。

在这里插入图片描述

为什么 TCP 断开连接要四次挥手

我们知道 TCP 采用三次握手策略让发送端和接收端都能确认双方收发功能OK,以此保证可靠传输。

为何断开却要四次?

注:
FIN 表示关闭连接
ACK 表示确认

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭,也就是客户端和服务端分别释放连接的过程,其实是客户端和服务端的两次挥手。

因为当服务端收到客户端的 FIN 数据包后(第一次挥手),服务端不会立即close,为什么不立即close?因为可能还有数据没发完,服务端会先将 ACK 发过去告诉客户端我收到你的断开请求了(第二次挥手),但请再给我一点时间,这段时间用来发送剩下的数据报文,发完之后再将 FIN 包发给客户端表示现在可以断了(第三次挥手)。客户端收到 FIN 包后发送 ACK 确认断开信息给服务端(第四次挥手)。

为什么TIME_WAIT状态需要经过2MSL才能进入CLOSED状态

MSL是TCP报文里面最大生存时间,超过这个时间报文将被丢弃。

理论上,四个报文都发送完毕,就可以直接进入CLOSED状态了,但是网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对连接释放报文段(FIN-ACK)的确认报文(ACK)。这个时候服务器会超时重传这个连接释放报文段(FIN-ACK),接着客户端再重传一次确认,重新启动2MSL时间等待计时器,最后客户端和服务端都进入到CLOSED状态,都能正常的关闭。假如客户端在TIME-WAIT状态不等待2MSL,而是在发送完确认报文(ACK)之后立即释放连接,一旦这个确认报文(ACK)丢失的话,则无法收到服务端重传的连接释放报文段(FIN-ACK),所以不会再发送一次确认报文,服务器收不到 确认报文 就无法正常的进入CLOSED关闭连接状态。

参考

查看原文

查看全部文章

博文系列目录

交流

各系列文章汇总:https://github.com/yuanyuanbyte/Blog

我是圆圆,一名深耕于前端开发的攻城狮。

weixin