从输入 URL 到看到页面发生了什么?
- DNS 解析
- 发起 TCP 连接
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 连接结束
DNS 解析
DNS 解析实际上就是寻找你所需要的资源的过程。假设你输入www.baidu.com,而这个网址并不是百度的真实地址,互联网中每一台机器都有唯一标识的IP地址,这个才是关键,但是它不好记,乱七八糟一串数字谁记得住啊,所以就需要一个网址和IP地址的转换,也就是DNS解析。
解析过程
DNS 解析是一个递归的过程
- 输入
www.google.com
网址后,首先在本地的域名服务器中查找 - 没找到去根域名服务器(.)查找
- 没有再去顶级域名服务器(.com)查找
- 如上类推,直到找到为止 (. -> .com -> google.com. -> <www.google.com>.)
DNS 缓存
DNS 存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS 服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。
chrome 浏览器的 DNS 缓存: chrome://net-internals/#dns
DNS 负载均衡
你访问 baidu.com 的时候,每次响应的并非是同一个服务器(IP 地址不同),一般大公司都有成百上千台服务器来支撑访问,假设只有一个服务器,那它的性能和存储量要多大才能支撑这样大量的访问呢?DNS 可以返回一个合适的机器的 IP 给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,这种过程就是 DNS 负载均衡。
发起 TCP 连接
TCP 提供一种可靠的传输,这个过程涉及到三次握手,四次挥手。
三次握手
- 客户端发送 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 连接都将被一直保持下去。
为什么会采用三次握手,若采用二次握手可以吗? 四次呢?
建立连接的过程是利用客户服务器模式,假设主机 A 为客户端,主机 B 为服务器端。 采用三次握手是为了防止失效的连接请求报文段突然又传送到主机 B,因而产生错误。失效的连接请求报文段是指:主机 A 发出的连接请求没有收到主机 B 的确认,于是经过一段时间后,主机 A 又重新向主机 B 发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机 A 第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机 B,主机 B 以为是主机 A 又发起的新连接,于是主机 B 同意连接,并向主机 A 发回确认,但是此时主机 A 根本不会理会,主机 B 就一直在等待主机 A 发送数据,导致主机 B 的资源浪费。 采用两次握手不行,原因就是上面说的失效的连接请求的特殊情况。而在三次握手中, client 和 server 都有一个发 syn 和收 ack 的过程, 双方都是发后能收, 表明通信则准备工作 OK. 为什么不是四次握手呢? 大家应该知道通信中著名的蓝军红军约定, 这个例子说明, 通信不可能 100%可靠, 而上面的三次握手已经做好了通信的准备工作, 再增加握手, 并不能显著提高可靠性, 而且也没有必要。
四次挥手
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于 ESTABLISHED 状态,假设客户端主动关闭,服务器被动关闭。
具体过程
- 客户端发送一个 FIN,用来关闭客户端到服务器的数据传送,也就是客户端告诉服务器:我已经不 会再给你发数据了(当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,客户端依然会重发这些数据),但是,此时客户端还可 以接受数据。FIN=1,其序列号为 seq=u(等于前面已经传送过来的数据的最后一个字节的序号加 1),此时,客户端进入 FIN-WAIT-1(终止等待 1)状态。 TCP 规定,FIN 报文段即使不携带数据,也要消耗一个序号。
- 服务器收到 FIN 包后,发送一个 ACK 给对方并且带上自己的序列号 seq,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号)。此时,服务端就进入了 CLOSE-WAIT(关闭等待)状态。TCP 服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个 CLOSE-WAIT 状态持续的时间。此时,客户端就进入 FIN-WAIT-2(终止等待 2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
- 服务器发送一个 FIN,用来关闭服务器到客户端的数据传送,也就是告诉客户端,我的数据也发送完了,不会再给你发数据了。由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为 seq=w,此时,服务器就进入了 LAST-ACK(最后确认)状态,等待客户端的确认。
- 主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号+1,此时,客户端就进入了 TIME-WAIT(时间等待)状态。注意此时 TCP 连接还没有释放,必须经过 2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的 TCB 后,才进入 CLOSED 状态。
服务器只要收到了客户端发出的确认,立即进入 CLOSED 状态。同样,撤销 TCB 后,就结束了这次的 TCP 连接。可以看到,服务器结束 TCP 连接的时间要比客户端早一些。 至此,完成四次挥手。
发送 HTTP 请求
HTTP 的端口为 80/8080,而 HTTPS 的端口为 443
发送 HTTP 请求的过程就是构建 HTTP 请求报文并通过 TCP 协议中发送到服务器指定端口 请求报文由 请求行
, 请求抱头
, 请求正文
组成。
请求行
请求行的格式为 Method Request-URL HTTP-Version CRLF
eg: GET index.html HTTP/1.1
常用的方法有: GET
, POST
, PUT
, DELETE
, OPTIONS
, HEAD
.
常见的请求方法区别
这里主要展示 POST 和 GET 的区别
- 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 里面藏 body,POST 里面带参数
GET
会产生一个 TCP
数据包,而 POST
会产生两个 TCP
数据包。 详细的说就是:
对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据);
而对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)。
注意一点,并不是所有的浏览器都会发送两次数据包,Firefox 就发送一次
请求报头
请求报头允许客户端向服务器传递请求的附加信息和客户端自身的信息。
请求报头中使用了 Accept
, Accept-Encoding
, Accept-Language
, Cache-Control
, Connection
, Cookie
等字段。 Accept
用于指定客户端用于接受哪些类型的信息,Accept-Encoding
与 Accept
类似,它用于指定接受的编码方式。 Connection
设置为 Keep-alive
用于告诉客户端本次 HTTP
请求结束之后并不需要关闭 TCP
连接,这样可以使下次 HTTP
请求使用相同的 TCP
通道,节省 TCP
连接建立的时间。
请求正文
当使用 POST
, PUT
等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的 Web 应用通常采用 Rest 架构,请求的数据格式一般为 json。这时就需要设置 Content-Type: application/json
。
HTTP 缓存
强制缓存
当缓存数据库中有客户端需要的数据,客户端直接将数据从其中拿出来使用(如果数据未失效),当缓存服务器没有需要的数据时,客户端才会向服务端请求。
缓存方案:
对于强制缓存,服务器响应的 header
中会用两个字段来表明 —— Expires
和 Cache-Control
。
Expires
Exprires 的值为服务端返回的数据到期时间。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差,另一方面,Expires 是 HTTP1.0 的产物,故现在大多数使用 Cache-Control 替代。
Cache-Control
Cache-Control 有很多属性,不同的属性代表的意义也不同。
- private:客户端可以缓存
- public:客户端和代理服务器都可以缓存
- max-age=t:缓存内容将在 t 秒后失效
- no-cache:需要使用协商缓存来验证缓存数据
- no-store:所有内容都不会缓存
协商缓存
又称对比缓存。客户端会先从缓存数据库拿到一个缓存的标识,然后向服务端验证标识是否失效,如果没有失效服务端会返回 304,这样客户端可以直接去缓存数据库拿出数据,如果失效,服务端会返回新的数据
缓存方案:
- Last-Modified: 服务器在响应请求时,会告诉浏览器资源的最后修改时间。
- Etag: 服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)
强制缓存的优先级高于协商缓存,若两种缓存皆存在,且强制缓存命中目标,则协商缓存不再验证标识。
但是实际应用中由于 Etag 的计算是使用算法来得出的,而算法会占用服务端计算的资源,所有服务端的资源都是宝贵的,所以就很少使用 Etag 了。
缓存的优点
- 减少了冗余的数据传递,节省宽带流量
- 减少了服务器的负担,大大提高了网站性能
- 加快了客户端加载网页的速度 这也正是 HTTP 缓存属于客户端缓存的原因。
不同刷新的请求执行过程
浏览器地址栏中写入 URL,回车
浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿。(最快)
F5
F5 就是告诉浏览器,别偷懒,好歹去服务器看看这个文件是否有过期了。于是浏览器就战战兢兢的发送一个请求带上 If-Modify-since。
Ctrl + F5
告诉浏览器,你先把你缓存中的这个文件给我删了,然后再去服务器请求个完整的资源文件下来。于是客户端就完成了强行更新的操作。
服务器处理请求并返回 HTTP 报文
状态码: 是由 3 位数组成,第一个数字定义了响应的类别,且有五种可能取值:
- 1xx:指示信息–表示请求已接收,继续处理。
- 2xx:成功–表示请求已被成功接收、理解、接受。
- 3xx:重定向–要完成请求必须进行更进一步的操作。
- 4xx:客户端错误–请求有语法错误或请求无法实现。
- 5xx:服务器端错误–服务器未能实现合法的请求。
平时遇到比较常见的状态码有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500
常见状态码:
- 200 成功 -> 请求成功,通常服务器提供了需要的资源。
- 204 无内容 -> 服务器成功处理了请求,但没有返回任何内容。
- 301 永久移动 -> 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
- 302 临时移动 -> 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
- 304 未修改 -> 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
- 400 错误请求 -> 服务器不理解请求的语法。
- 401 未授权 -> 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
- 403 禁止 -> 服务器拒绝请求。
- 404 未找到 -> 服务器找不到请求的网页。
- 422 无法处理 -> 请求格式正确,但是由于含有语义错误,无法响应
- 500 服务器内部错误 -> 服务器遇到错误,无法完成请求。