Interview

校招准备中,fighting!
答案主要是网上一些资料的整理

输入网址之后发生了什么?

  1. 输入网址
  2. 浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
    • 如果资源未缓存,发起新请求
    • 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
    • 检验新鲜通常有两个HTTP头进行控制Expires和Cache-Control:
  3. 浏览器解析URL获取协议,主机,端口,path,浏览器查找域名对应IP
    3.1 DNS查找过程

    1. Chrome浏览器 会首先搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)(2-30分钟不等,视浏览器而定)
    2. 如果浏览器自身的缓存里面没有找到对应的条目,那么Chrome会搜索操作系统自身的DNS缓存,如果找到且没有过期则停止搜索解析到此结束.
    3. 如果在Windows系统的DNS缓存也没有找到,那么尝试读取hosts文件(位于C:\Windows\System32\drivers\etc),看看这里面有没有该域名对应的IP地址,如果有则解析成功
    4. 如果在hosts文件中也没有找到对应的条目,浏览器就会发起一个DNS的系统调用,就会向本地配置的首选DNS服务器(一般是电信运营商提供的,也可以使用像Google提供的DNS服务器)发起域名解析请求(通过的是UDP协议向DNS的53端口发起请求,这个请求是递归的请求,也就是运营商的DNS服务器必须得提供给我们该域名的IP地址),运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。如果没有找到对应的条目,则有运营商的DNS代我们的浏览器发起迭代DNS解析请求,它首先是会找根域的DNS的IP地址(这个DNS服务器都内置13台根域的DNS的IP地址),找打根域的DNS地址,就会向其发起请求(请问www.linux178.com这个域名的IP地址是多少啊?),根域发现这是一个顶级域com域的一个域名,于是就告诉运营商的DNS我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去,于是运营商的DNS就得到了com域的IP地址,又向com域的IP地址发起了请求(请问www.linux178.com这个域名的IP地址是多少?),com域这台服务器告诉运营商的DNS我不知道www.linux178.com这个域名的IP地址,但是我知道linux178.com这个域的DNS地址,你去找它去,于是运营商的DNS又向linux178.com这个域名的DNS地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求(请问www.linux178.com这个域名的IP地址是多少?),这个时候linux178.com域的DNS服务器一查,诶,果真在我这里,于是就把找到的结果发送给运营商的DNS服务器,这个时候运营商的DNS服务器就拿到了www.linux178.com这个域名对应的IP地址,并返回给Windows系统内核,内核又把结果返回给浏览器,终于浏览器拿到了www.linux178.com对应的IP地址,该进行一步的动作了。
  4. 打开一个socket与目标IP地址,端口建立TCP链接
    通过 Socket 发送数据
    当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443)就可以通过 Socket API 来发送数据了,这时可以选择 TCP 或 UDP 协议,HTTP 常用的是 TCP 协议。它会调用系统库函数 socket ,请求一个 TCP流套接字,对应的参数是 AF_INET 和 SOCK_STREAM

    • 这个请求首先被交给传输层,在传输层请求被封装成 TCP segment。目标端口会会被加入头部,源端口会在系统内核的动态端口范围内选取(Linux下是ip_local_port_range)
    • TCP segment 被送往网络层,网络层会在其中再加入一个 IP 头部,里面包含了目标服务器的IP地址以及本机的IP地址,把它封装成一个TCP packet。
    • 这个 TCP packet 接下来会进入链路层,链路层会在封包中加入 frame头 部,里面包含了本地内置网卡的MAC地址以及网关(本地路由器)的 MAC 地址。像前面说的一样,如果内核不知道网关的 MAC 地址,它必须进行 ARP 广播来查询其地址。

      到了现在,TCP 封包已经准备好了,可以使用以太网、WiFi、蜂窝数据网络进行传输。大型企业和比较新的住宅通常使用光纤或直接以太网连接,这种情况下信号一直是数字的,会被直接传到下一个 网络节点 进行处理。

      最终封包会到达管理本地子网的路由器。在那里出发,它会继续经过自治区域的边界路由器,其他自治区域,最终到达目标服务器。一路上经过的这些路由器会从IP数据报头部里提取出目标地址,并将封包正确地路由到下一个目的地。IP数据报头部TTL域的值每经过一个路由器就减1,如果封包的TTL变为0,或者路由器由于网络拥堵等原因封包队列满了,那么这个包会被路由器丢弃。

      TCP 3次握手
      tcp

      1) Client首先发送一个连接试探,ACK=0 表示确认号无效,SYN = 1 表示这是一个连接请求或连接接受报文,同时表示这个数据报不能携带数据,seq = x 表示Client自己的初始序号(seq = 0 就代表这是第0号包),这时候Client进入syn_sent状态,表示客户端等待服务器的回复
      2) Server监听到连接请求报文后,如同意建立连接,则向Client发送确认。TCP报文首部中的SYN 和 ACK都置1 ,ack = x + 1表示期望收到对方下一个报文段的第一个数据字节序号是x+1,同时表明x为止的所有数据都已正确收到(ack=1其实是ack=0+1,也就是期望客户端的第1个包),seq = y 表示Server 自己的初始序号(seq=0就代表这是服务器这边发出的第0号包)。这时服务器进入syn_rcvd,表示服务器已经收到Client的连接请求,等待client的确认。
      3) Client收到确认后还需再次发送确认,同时携带要发送给Server的数据。ACK 置1 表示确认号ack= y + 1 有效(代表期望收到服务器的第1个包),Client自己的序号seq= x + 1(表示这就是我的第1个包,相对于第0个包来说的),一旦收到Client的确认之后,这个TCP连接就进入Established状态,就可以发起http请求了。

      建立TCP连接后发起http请求

    1. 请求报文
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      起始行:如 GET / HTTP/1.0 (请求的方法  请求的URL 请求所使用的协议)
      头部信息:User-Agent Host等成对出现的值
      主体

      请求方法有
      GET: 完整请求一个资源 (常用)
      HEAD: 仅请求响应首部
      POST:提交表单 (常用)
      PUT: (webdav) 上传
      DELETE:(webdav) 删除
      OPTIONS:返回请求的资源所支持的方法的方法
      TRACE: 追求一个资源请求中间所经过的代理
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Accept:*/*
Accept-Encoding:gzip, deflate, sdch, br
Accept-Language:zh-CN,zh;q=0.8
Connection:keep-alive
Cookie:***
Host:mpp.taobao.com
Referer:https://mpp.taobao.com/ajaxconn2.html?appId=1064&token=1cbd5761858c5cfb7ff98da434af6f7766e517db5498216576b9k1s2x4b0f1c35&domain=taobao.org
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36

Accept 就是告诉服务器端,我接受那些MIME类型
Accept-Encoding 这个看起来是接受那些压缩方式的文件
Accept-Lanague 告诉服务器能够发送哪些语言
Connection 告诉服务器支持keep-alive特性
Cookie 每次请求时都会携带上Cookie以方便服务器端识别是否是同一个客户端
Host 用来标识请求服务器上的那个虚拟主机,比如Nginx里面可以定义很多个虚拟主机
那这里就是用来标识要访问那个虚拟主机。
User-Agent 用户代理,一般情况是浏览器,也有其他类型,如:wget curl 搜索引擎的蜘蛛等

条件请求首部:
If-Modified-Since 是浏览器向服务器端询问某个资源文件如果自从什么时间修改过,那么重新发给我,这样就保证服务器端资源
文件更新时,浏览器再次去请求,而不是使用缓存中的文件
安全请求首部:
Authorization: 客户端提供给服务器的认证信息;
  1. 到达服务器

    • 负载均衡

      请求在进入到真正的应用服务器前,可能还会先经过负责负载均衡的机器,它的作用是将请求合理地分配到多个服务器上,同时具备具备防攻击等功能。
      负载均衡具体实现有很多种,有直接基于硬件的 F5,有操作系统传输层(TCP)上的 LVS,也有在应用层(HTTP)实现的反向代理(也叫七层代理),接下来将介绍 LVS 及反向代理。

      负载均衡的策略也有很多,如果后面的多个服务器性能均衡,最简单的方法就是挨个循环一遍(Round-Robin)

    • LVS
      LVS 的作用是从对外看来只有一个 IP,而实际上这个 IP 后面对应是多台机器,因此也被成为 Virtual IP。

    • 反向代理
      向代理是工作在 HTTP 上的,具体实现可以基于 HAProxy 或 Nginx,因为反向代理能理解 HTTP 协议,所以能做非常多的事情,比如:进行很多统一处理,比如防攻击策略、放抓取、SSL、gzip、自动性能优化等。应用层的分流策略都能在这里做,比如对 /xx 路径的请求分到 a 服务器,对 路径的请求分到 b 服务器,或者按照 cookie 进行小流量测试等
      缓存,并在后端服务挂掉的时候显示友好的 404 页面
      监控后端服务是否异常
  2. 重定向
    当我们输入不完整的网址
    时,或者网站迁移做了重定向设置时,服务器会进行一次重定向响应。 重定向之后会发布一个新的获取请求

  3. 服务器处理请求

web服务器软件(如IIS或者Apache)接收到HTTP请求
确定执行那个请求处理程序
请求处理器阅读请求头的参数和cookies信息
更新服务器上的信息:例如更新数据库信息、服务端cookies
生成HTML,压缩(gzip或其他),响应请求发送给用户

  • 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
  • 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
  • 服务器将响应报文通过TCP连接发送回浏览器
  1. 浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下
    • 主动方发送Fin=1, Ack=Z, Seq= X报文
    • 被动方发送ACK=X+1, Seq=Z报文
    • 被动方发送Fin=1, ACK=X, Seq=Y报文
    • 主动方发送ACK=Y, Seq=X报文
  2. 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
  3. 如果资源可缓存,进行缓存
  4. 对响应进行解码(例如gzip压缩)
  5. 根据资源类型决定如何处理(假设资源为HTML文档)
    服务器发回一个HTML响应

响应包括响应头(响应参数与信息)、响应包(主体文件)
响应包采用特定方法压缩,整个响应以blob类型传输,响应头指示响应包以何种方式压缩
这个响应头与重定向的响应头不太一样,这个响应头还包含着缓存选项,cookies设置和隐私信息等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
connection:keep-alive
Content-Type:text/html;charset=utf-8
date:Sat, 23 Jul 2016 04:15:48 GMT
server:Cowboy
transfer-encoding:chunked

Connection 使用keep-alive特性
Content-type MIME类型为html类型,字符集是 UTF-8
Date 响应的日期
Server 使用的WEB服务器
Transfer-Encoding:chunked 分块传输编码 是http中的一种数据传输机制,允许HTTP由网页服务器发送给客户端应用(通常是网页浏览器)的数据可以分成多个部分,分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供

获取静态资源时
Content-Encoding 资源压缩
Cache-Control:max-age=3600
Expires:Sat, 23 Jul 2016 06:11:08 GMT
Last-Modified:Thu, 24 Sep 2015 12:50:17 GMT

解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序

  • 构建DOM树
    • Tokenizing:根据HTML规范将字符流解析为标记
    • Lexing:词法分析将标记转换为对象并定义属性和规则
    • DOM construction:根据HTML标记关系将对象组成DOM树
      加载过程中遇到外部css文件,浏览器发出一个请求,来获取css文件。
      遇到图片资源,浏览器也会发出一个请求,来获取图片资源。这是异步请求,并不会影响html文档进行加载,但是当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。虽然css文件的加载不影响js文件的加载,但是却影响js文件的执行,即使js文件内只有一行代码,也会造成阻塞。more
  • 构建CSSOM树
    • Tokenizing:字符流转换为标记流
    • Node:根据标记创建节点
    • CSSOM:节点创建CSSOM树
  • 根据DOM树和CSSOM树构建渲染树:
    • 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none
    • 对每一个可见节点,找到恰当的CSSOM规则并应用
    • 发布可视节点的内容和计算样式
  • js解析如下:
    • 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
    • HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容
    • 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素
    • 当文档完成解析,document.readState变成interactive
    • 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
    • 浏览器在Document对象上触发DOMContentLoaded事件
    • 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
      12.显示页面(HTML解析过程中会逐步显示页面)

more
浏览器高层架构

组成浏览器的组件有:

  • 用户界面 用户界面包含了地址栏,前进后退按钮,书签菜单等等,除了请求页面之外所有你看到的内容都是用户界面的一部分
  • 浏览器引擎 浏览器引擎负责让 UI 和渲染引擎协调工作
  • 渲染引擎 渲染引擎负责展示请求内容。如果请求的内容是 HTML,渲染引擎会解析 HTML 和 CSS,然后将内容展示在屏幕上
  • 网络组件 网络组件负责网络调用,例如 HTTP 请求等,使用一个平台无关接口,下层是针对不同平台的具体实现
  • UI后端 UI 后端用于绘制基本 UI 组件,例如下拉列表框和窗口。UI 后端暴露一个统一的平台无关的接口,下层使用操作系统的 UI 方法实现
  • Javascript 引擎 Javascript 引擎用于解析和执行 Javascript 代码
  • 数据存储 数据存储组件是一个持久层。浏览器可能需要在本地存储各种各样的数据,例如 Cookie 等。浏览器也需要支持诸如 localStorage,IndexedDB,WebSQL 和 FileSystem 之类的存储机制

浏览器获取其他文件
浏览器解析HTML遇到需要下载的文件时,便再次向服务器(CDN)发送获取文件的请求。

  • 动态页面无法缓存,静态文件允许浏览器进行缓存。
  • 静态文件本地有缓存时直接从本地读取
  • 请求响应头内包含着静态文件保存的期限,浏览器知道下载的静态文件要静默保留多久。
  • 响应头还会有静态文件的ETag(相当于版本号),当浏览器发现请求的静态文件的响应头的ETag与现有的缓存文件不符时,便会再次向服务器获取静态文件。