# 三次握手和四次挥手

在开始之前,先简单说一下 TCP 连接中比较常用的五个状态:

  • SYN 表示建立连接
  • FIN 表示关闭连接
  • ACK 表示响应
  • PSH 表示 data 数据传输
  • RST 表示连接重置

其中 ACK 是可能与 SYN , FIN 等同时使用的, 比如 SYN 和 ACK 可能同时为 1, 它表示的就是建立连接之后的响应;如果只是单个的一个 SYN,表示的只是建立连接。TCP 的几次握手就是通过这样的 ACK 表现出来的。

但是 SYN 与 FIN 不会同时为 1,因为前者表示的是建立连接,后者表示的是断开连接。RST 一般是在 FIN 之后才会出现 1 的情况,表示重新连接。一般出现 FIN 包或者 RST 包时,我们就认为客户端与服务端断开了连接;当出现 SYN 和 SYN + ACK 包时, 我们就认为客户端和服务器建立了一个连接。RSH 为 1 的情况一般只出现在 data 内容不为 0 的包中,也就是说 PSH 为 1 表示的是真正有 TCP 数据包内容被传输。

TCP 的连接建立和连接关闭,都是通过请求-响应的模式完成的。

位码是 tcp 的标志位,标识:

  1. Sequence number 顺序号码
  2. Acknowledge number 确认号码

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

# 过程

  • 第一次握手:建立连接时,客户端发送 syn 包(syn=j) 到服务器,并进入 SYN_SEND 状态,等待服务器确认。
  • 第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN-RECEIVED 状态;
  • 第三次握手:客户端收到服务器的 SYN + ACK 包,向服务器发送确认包 ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态(已建立),完成三次握手。完成三次握手,客户端与服务器开始传送数据.

完成三次握手之后,主机 A 与主机 B 就开始传输数据。

# 为什么要进行三次握手

  • 在第一次通信过程中,A 向 B 发送信息之后,B 收到信息后可以确认自己的收信能力和 A 的发信能力没有问题。
  • 在第二次通信中,B 向 A 发送信息之后,A 可以确认自己的发信能力和 B 的收信能力没有问题,但是 B 不知道自己的发信能力到底如何,所以就需要第三次通信。
  • 在第三次通信中,A 向 B 发送信息之后,B 就可以确认自己的发信能力没有问题。

两次不够,四次没必要。

# 四次挥手

TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。

  • 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。发送完毕后,客户端进入 FIN_WAIT_1 状态。
  • 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。
  • 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个 ACK。
  • 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

# 从输入 URL 到页面加载完成的过程

  1. 在浏览器地址栏输入 URL
  2. 判断是否有永久重定向(301)
    1. 如果有,直接跳转到对应 URL
  3. 浏览器查看资源是否有强缓存,有则直接使用,如果是协商缓存则需要到服务器进行校验资源是否可用
    1. 检验新鲜通常有两个 HTTP 头进行控制ExpiresCache-Control
      • HTTP1.0 提供 Expires,值为一个绝对时间表示缓存新鲜日期
      • HTTP1.1 增加了 Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
  4. 浏览器解析 URL获取协议,主机,端口,path
  5. 浏览器组装一个 HTTP(GET)请求报文
  6. DNS 解析,查找过程如下:
    1. 浏览器缓存
    2. 本机缓存
    3. hosts 文件
    4. 路由器缓存
    5. ISP DNS 缓存
    6. DNS 查询(递归查询 / 迭代查询)
  7. 端口建立 TCP 连接,三次握手如下:
    1. 客户端发送一个 TCP 的SYN=1,Seq=X的包到服务器端口
    2. 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
    3. 客户端发送ACK=Y+1, Seq=Z
  8. TCP 连接建立后发送 HTTP 请求
  9. 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用 HTTP Host 头部判断请求的服务程序
  10. 服务器检查HTTP 请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
  11. 处理程序读取完整请求并准备 HTTP 响应,可能需要查询数据库等操作
  12. 服务器将响应报文通过 TCP 连接发送回浏览器
  13. 浏览器接收 HTTP 响应,然后根据情况选择关闭 TCP 连接或者保留重用,关闭 TCP 连接的四次挥手如下
    1. 主动方发送Fin=1, Ack=Z, Seq= X报文
    2. 被动方发送ACK=X+1, Seq=Z报文
    3. 被动方发送Fin=1, ACK=X, Seq=Y报文
    4. 主动方发送ACK=Y, Seq=X报文
  14. 浏览器检查响应状态吗:是否为 1XX,3XX, 4XX, 5XX,这些情况处理与 2XX 不同
  15. 如果资源可缓存,进行缓存
  16. 对响应进行解码(例如 gzip 压缩)
  17. 根据资源类型决定如何处理(假设资源为 HTML 文档)
  18. 解析 HTML 文档,构件 DOM 树,下载资源,构造 CSSOM 树,执行 js 脚本,这些操作没有严格的先后顺序,以下分别解释
  19. 构建 DOM 树
    1. Tokenizing:根据 HTML 规范将字符流解析为标记
    2. Lexing:词法分析将标记转换为对象并定义属性和规则
    3. DOM construction:根据 HTML 标记关系将对象组成 DOM 树
  20. 解析过程中遇到图片、样式表、js 文件,启动下载
  21. 构建CSSOM 树
    1. Tokenizing:字符流转换为标记流
    2. Node:根据标记创建节点
    3. CSSOM:节点创建 CSSOM 树
  22. 根据 DOM 树和 CSSOM 树构建渲染树:
    1. 从 DOM 树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被 css 隐藏的节点,如display: none
    2. 对每一个可见节点,找到恰当的 CSSOM 规则并应用
    3. 发布可视节点的内容和计算样式
  23. js 解析如下
    1. 浏览器创建 Document 对象并解析 HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate 为 loading
    2. HTML 解析器遇到没有 async 和 defer 的 script 时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用 document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作 script 和他们之前的文档内容
    3. 当解析器遇到设置了async属性的 script 时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用 document.write(),它们可以访问自己 script 和之前的文档元素
    4. 当文档完成解析,document.readState 变成 interactive
    5. 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用 document.write()
    6. 浏览器在 Document 对象上触发 DOMContentLoaded 事件
    7. 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState 变为 complete,window 触发 load 事件
  24. 显示页面(HTML 解析过程中会逐步显示页面)

浏览器的工作流程大致就是:构建 DOM 树-构建 CSSOM-构建渲染树-布局-绘制

# CDN

CDN(Content Delivery Network,内容分发网络)是构建在现有互联网基础之上的一层智能虚拟网络,通过在网络各处部署节点服务器,实现将源站内容分发至所有 CDN 节点,使用户可以就近获得所需的内容。CDN 服务缩短了用户查看内容的访问延迟,提高了用户访问网站的响应速度与网站的可用性,解决了网络带宽小、用户访问量大、网点分布不均等问题。

CDN 主要有以下优点:

  • 更快地将数据分发给用户;
  • 通过部署多台服务器,从而提高系统整体的带宽性能;
  • 多台服务器可以看成是一种冗余机制,从而具有高可用性。

# 加速原理

当用户访问使用 CDN 服务的网站时,本地 DNS 服务器通过 CNAME 方式将最终域名请求重定向到 CDN 服务。CDN 通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的 CDN 节点 IP 地址提供给用户,使用户可以以最快的速度获得网站内容。使用 CDN 后的 HTTP 请求处理流程如下:

# CDN 的核心功能

CDN 的核心点有两个,一个是缓存,一个是回源

  • “缓存”就是说我们把资源 copy 一份到 CDN 服务器上这个过程
  • “回源”就是说 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程。

# CDN 节点有缓存场景

  1. 用户在浏览器输入要访问的网站域名,向本地 DNS 发起域名解析请求。
  2. 域名解析的请求被发往网站授权 DNS 服务器。
  3. 网站 DNS 服务器解析发现域名已经 CNAME 到了 www.example.com.c.cdnhwc1.com。
  4. 请求被指向 CDN 服务。
  5. CDN 对域名进行智能解析,将响应速度最快的 CDN 节点 IP 地址返回给本地 DNS。
  6. 用户获取响应速度最快的 CDN 节点 IP 地址。
  7. 浏览器在得到速度最快节点的 IP 地址以后,向 CDN 节点发出访问请求。
  8. CDN 节点将用户所需资源返回给用户。

# 为什么传统上利用多个域名来提供网站资源会更有效?

  1. CDN 缓存更方便
  2. 突破浏览器并发限制(一般每个域名建立的链接不超过 6 个)
  3. Cookie less,节省带宽,尤其是上行带宽一般比下行要慢
  4. 对于 UGC 的内容和主站隔离,防止不必要的安全问题(上传 js 窃取主站 cookie 之类的)。正是这个原因要求用户内容的域名必须不是自己主站的子域名,而是一个完全独立的第三方域名。
  5. 数据做了划分,甚至切到了不同的物理集群,通过子域名来分流比较省事。这个可能被用的不多

ps:

关于 Cookie 的问题,带宽是次要的,安全隔离才是主要的。关于多域名,也不是越多越好,虽然服务器端可以做泛解释,浏览器做 dns 解释也是耗时间的,而且太多域名,如果要走 https 的话,还有要多买证书和部署的问题

# 将静态资源放在其他域名的目的是什么?

这样做的主要目的是在请求这些静态资源的时候不会发送 cookie,节省了流量,需要注意的是 cookie 是会发送给子域名的(二级域名),所以这些静态资源是不会放在子域名下的,而是单独放在一个单独的主域名下。同时还有一个原因就是浏览器对于一个域名会有请求数的限制,这种方法可以方便做 CDN。

首先,CDN 服务器域名与业务服务器域名不一致。

例如淘宝,业务服务器域名为“www.taobao.com”,而 CDN 服务器的域名是“g.alicdn.com”

Cookie 是紧跟域名的。同一个域名下的所有请求,都会携带 Cookie。大家试想,如果我们此刻仅仅是请求一张图片或者一个 CSS 文件,我们也要携带一个 Cookie 跑来跑去(关键是静态资源往往并不需要 Cookie 携带什么认证信息),这是一件多么劳民伤财的事情。Cookie 虽然小,请求却可以有很多,随着请求的叠加,这样的不必要的 Cookie 带来的开销将是很大的。

# DNS

DNS 的作用就是通过域名查询到具体的 IP。

因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么。

在 TCP 握手之前就已经进行了 DNS 查询,这个查询是操作系统自己做的。当你在浏览器中想访问 www.google.com 时,会进行一下操作:

  1. 操作系统会首先在本地缓存中查询
  2. 没有的话会去系统配置的 DNS 服务器中查询
  3. 如果这时候还没得话,会直接去 DNS 根服务器查询,这一步查询会找出负责 com 这个一级域名的服务器
  4. 然后去该服务器查询 google 这个二级域名
  5. 接下来三级域名的查询其实是我们配置的,你可以给 www 这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP

以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。

PS:DNS 是基于 UDP 做的查询。

# DNS 域名系统,简单描述其工作原理。

DNS 客户机需要在程序中使用名称时,它会查询 DNS 服务器来解析该名称。客户机发送的每条查询信息包括三条信息:包括:指定的 DNS 域名,指定的查询类型,DNS 域名的指定类别。基于 UDP 服务,端口 53. 该应用一般不直接为用户使用,而是为其他应用服务,如 HTTP,SMTP 等在其中需要完成主机名到 IP 地址的转换。