通用问题

网络分层模型

TCP/IP 四层模型:应用、传输、网络、网络接口;

TCP/IP 五层模型(主流):应用、传输、网络、数据链路、物理;

OSI 七层模型:

  • 应用:http、ftp
  • 表示:telnet
  • 会话:dns
  • 传输:udp、tcp
  • 网络:ip
  • 数据链路:以太网协议
  • 物理;各种物理连接设备

TCP

TCP 是传输可靠、有连接、全双工的传输层协议;

三次握手:

  1. C to S:SYN=1,seq=x;
  2. S to C:SYN=1,ACK=1,seq=y,ack=x+1;
  3. C to S:ACK=1,seq=x+1,ack=y+1;

四次挥手:

  1. C to S:FIN=1,seq=u;
  2. S to C:ACK=1,seq=v,ack=u+1,服务器继续发送旧数据;
  3. S to C:FIN=1,ACK=1,seq=w,ack=u+1,服务器停止发送任何数据;
  4. C to S:ACK=1,seq=u+1,ack=w+1;

第四次挥手后,客户端等待 2MSL 后再断开连接,否则如果服务器没有收到第四次挥手报文,将重复发送第三次挥手报文,由于此时客户端已经断开,服务器永远无法收到第四次挥手报文,将导致后续连接和数据包的混乱;

为什么是三次握手?

确保双方同处于一个上下文中,使得信息同步,进而保证连接可靠性;

如果是一次握手,显然发起方不能确认连接是否成功建立;

如果是两次握手,接收方将无法确认发起方的第一次握手是否还有效,如果发起方第一次握手后放弃建立连接(非断开连接),客户端将无法感知到。

为什么是四次挥手?

相比三次握手,四次挥手多的是第二次挥手,这是因为第二次挥手是为了通知客户端还有旧数据要传输,传输完成后才可以说双方数据均以传输完毕,可以断开连接。

TCP 确保传输可靠的机制

  1. 序列号和确认号;
  2. 超时重传;
  3. 流量控制;
  4. 拥塞控制;

拥塞控制

滑动窗口的大小使用慢启动动态改变,一旦发生丢包或超时则启动快速重传快速恢复

飞包守恒原则:总是收到一个旧包的 ack 确认包才会发送新包;

慢启动:

  • 维护一个拥塞窗口大小,初始为指定倍 MSS(最大报文长度,双方协商的值),发送窗口大小取拥塞窗口与接受窗口的较小者,每 1RTT 拥塞窗口翻倍(每收到一个 ack,大小加一);
  • 维护一个慢启动阈值,初始为2^16 - 1,一旦拥塞窗口超过该值,启动拥塞避免算法;
  • 随着拥塞窗口指数级上升,到达阈值后变为线性上升,而一旦接收到三个相同 ack,就会启动快速重传快速恢复

超时重传:指定时间内没有收到 ack 确认包,重发超时的包;

冗余确认:发送的多个包,收到的却是相同的 ack 确认包(三个相同包),这时可以认定发生了丢包;

快速重传:确认丢包(冗余确认)后不必等待超时,立即重发丢失的包,之后启动快速恢复

快速恢复:丢包后,先把阈值置为拥塞窗口的一半,继续发送数据,过程中使拥塞窗口尽量平滑地降低到阈值附近,然后退出快速恢复状态;

拥塞避免:拥塞窗口大小累加 1MSS,开始线性增长。

UDP

什么是 UDP?

UDP 是一种尽力交付但不保证交付、无连接、面向报文的传输层协议,支持一对一,一对多,多对多的通信;

不可靠是因为不具有 TCP 的拥塞控制、流量控制等机制;

面向报文是指 UDP 不会拆分、组合应用层的报文,加上 UDP 首部之后就会直接发送;

IP

IP 协议是无连接、不可靠的网络层协议,用于将数据从源地址发送到目的地址;

IP 地址是一个 32 位的二进制数,由于 MAC 地址称为物理地址,所以 IP 地址也称逻辑地址,IP 地址解决了网络中如何将数据传输到指定端的问题;

IP 协议首部举例:

  • 源地址;
  • 目的地址;
  • TTL:每经过一个设备就减一,为零时必须丢弃,避免找不到目的地址的 IP 数据包在网络中无限传播;
  • IP 协议版本;
  • 数据协议;
  • 片偏移:数据包切分后用于标识偏移量;

DNS

DNS 是负责域名解析的会话层协议,由于 IP 地址不好记忆,所以可以注册一个域名来代替该 IP 地址,但由于 IP 协议不能直接识别域名,所以需要 DNS 协议从域名服务器获取到域名对应的 IP 地址;

DNS 解析过程:先查询本地 DNS 服务器的缓存,本地 DNS 服务器如果没有记录,则依次查询根、顶级、二级域名服务器,直到查询到 IP 地址;

SSL/TLS

第一阶段:

  1. Client Hello(SSL version,加密套件,session id 等);
  2. Server Hello(SSL version,加密套件,session id 等);

第二阶段:

  1. Server Certificate;
  2. Server Hello Done;

第三阶段:

  1. Client:Client Key Exchange(Client 端的 pre-master-secret);

第四阶段:

  1. Client:Change Cipher Spec;
  2. Client:Finished;
  3. Server:Change Cipher Spec;
  4. Server:Finished;

服务器证书由权威 CA 机构签发,其真实性如何保证?如下:

  1. 服务器提供数据(如服务器端生成的公钥),CA 机构使用自己的私钥为数据签名,生成数字证书,颁发给服务器;
  2. 浏览器和操作系统内置了各大权威 CA 机构的根证书,证书内就是 CA 机构的公钥,用于验证服务器证书的签名与内容是否一致(验证 HASH 值);

以上两点加粗内容就是整个证书互信机制的核心,可见一切都建立在 CA 机构私钥绝对安全,以及浏览器、操作系统根证书不被篡改的基础上,一旦这两点不能保证,则整套机制失去意义。

注 1:SSL 建立连接过程此处为了简便,默认两点,一是使用 RSA 算法,二是由服务器单方提供证书,而非双方都提供证书(类似于银行和“U 盾”的场景);

注 2:私钥加签,公钥验签;公钥加密,私钥解密;

HTTP

常见状态码

2xx:

  • 200 OK
  • 206 Partial Content

3xx:

  • 301 Moved Permanently
  • 304 Not Modified
  • 307 Temporary Redirect,临时重定向,但新请求不能改变请求方法;
  • 308 Permanent Redirect,永久重定向,但新请求不能改变请求方法;

4xx:

  • 400 Bad Request,请求的语法有误;
  • 403 Forbidden,服务器拒绝响应;
  • 404 Not Found,请求的资源不存在;
  • 405 Method Not Allowed,请求方法不合法;

5xx:

  • 500 Internal Server Error
  • 503 Service Unavailable,服务器宕机;

浏览器缓存机制

强缓存:由 Cache-Control、Expires 控制的缓存策略;

Cache-Control 相关指令:

  • 如果不希望复用 Response,使用 no-store;
  • 如果希望复用 Response
    • 希望每次都验证资源有效性,使用 no-cache;
    • 不希望每次都验证,不用 no-store 或 no-cache
      • 允许中间服务器缓存,使用 public;
      • (默认行为)不允许中间服务器缓存,使用 private;
  • 设置缓存有效时间,使用 max-age/s-max-age 指令,优先级高于 Expires 首部;

强缓存未命中的情况下,还有协商缓存策略:发送一条带有 if-none-match/if-modified-since 首部的条件请求到源服务器,服务器验证资源有效性,有效则返回状态码 304 Not Modified,失效则返回新资源,状态码 200 OK;

注 1:s-max-age 可覆盖 max-age 和 Expires,只能用于中间服务器缓存的时效控制;

注 2:其他时效控制指令还有 max-stale/min-fresh 等;

注 3:private 指令下,一般由浏览器本地缓存;

注 4:if-match 验证的是资源的 ETag,if-modified-since 验证的是资源的最近修改时间;

CORS

Cross-Origin Resource Sharing(跨域资源共享),包含一组扩展的 HTTP 首部,跨域的简单请求将直接发送,非简单请求将发送一个 method 为 options 的 preflight 请求;

请求首部:

  1. Access-Control-Request-Method
  2. Access-Control-Request-Headers
  3. Origin

响应首部:

  1. Access-Control-Allow-Methods
  2. Access-Control-Allow-Headers
  3. Access-Control-Expose-Headers
  4. Access-Control-Allow-Origin
  5. Access-Control-Allow-Credentials
  6. Access-Control-Max-Age

简单请求:

  • Method 为 Get/Post/Head;
  • 仅包含以下首部:
    • Other Auto-seted by user-agent headers(Connection/User-Agent);
    • Content-Type 仅包含:
      • application/x-www-form-urlencoded;
      • text/plain;
      • multipart/form-data;
    • Accept;
    • Accept-Language;
    • Content-Language;
    • Content-Type;
    • DPR;
    • Downlink;
    • Save-Data;
    • Viewport-Width;
    • Width;

注:简单、非简单请求为过时的称呼,现行规范中不再使用;

流程:

  1. 浏览器判断请求为:
    1. 简单请求,直接发送该请求;
    2. 非简单请求,则
      1. 发送 preflight 请求;
      2. 服务器响应该 preflight 请求;
      3. 发送真实请求;

简单请求只需要通过 Origin 和 Access-Control-Allow-Origin 就可以控制跨域资源的访问;
非简单请求还需要通过*-Request-Method\Headers 和*-Request-Methods\Headers 来控制跨域 HTTP 报文允许哪些 Method 和 Header,*-Max-Age 指定一段时间内无需再次发送 preflight 请求;

跨域访问限制与解决方案

通信双方的协议、域名和端口都相同,则称之为同源,否则就是不同源,不同源之间的通信称为跨域访问(跨源访问),浏览器对跨域访问有限制,为了解决该问题,有以下方案:

  1. CORS
  2. Websocket:websocket 不受跨域限制;
  3. Jsonp:提前定义好回调函数,构建 script 元素,将跨域请求 URL 写入 src,插入 DOM,由于静态资源加载允许跨域,所以可以发送跨域请求,服务器在返回的 js 片段中编写回调函数的调用代码,并将服务器端的数据以回调函数参数的形式拼接进去。
  4. 正向代理、反向代理:确保客户端与代理服务器不跨域,由代理服务器与目标服务器通信,服务器之间通信自然不受浏览器的规则限制;
  5. postMessage & onmessage:用于不同源 tab 或 iframe 之间跨域通信,postMessage 发送数据,onmessage 监听数据接收事件;

注:

  • 正向代理:向指定的客户端负责,任何这些客户端的请求,都通过该代理服务器代为发送;
  • 反向代理:向指定的服务端负责,任何这些服务端的响应,都通过该代理服务器代为发送;

HTTP 扩展首部:

  • set-cookie:服务器在 http 响应报文中通过该首部通知浏览器设置 cookie 的值;
  • cookie:浏览器在 http 请求报文中通过该首部将 cookie 发送到服务器;
  • 形式上如 key=value,多个键值对以分号;分隔;

cookie 指令:

  • 时效:
    • Expires=<date>:过期时间,默认为会话结束后过期;
    • Max-age=<seconds>:有效时间,小于等于 0 将直接失效,优先级高于 expires,兼容性差于 expires;
  • 目标:
    • Domain=<domain>:指定访问哪些域需要携带 cookie;
    • Path=<path>:指定访问哪些资源路径(含所有子路径)需要携带 cookie;
  • 安全:
    • Secure:只允许通过 https 携带 cookie;
    • HttpOnly:禁止 js 读写 cookie;
    • SameSite=<option>:是否允许以第三方 cookie 形式发送,可选项如下:
      • strict:完全禁止;
      • lax:大部分禁止,只允许 Get 请求携带(超链接、Get 表单、资源预加载);
      • none:不禁止;

注:浏览器禁止 cookie 跨域读写,但允许跨域请求,即 B 不能读写 A 的 cookie,但 B 对 A 可发送请求,这个请求可以携带 A 的 cookie,由于 B 页面和用户是第一二方,A 属于第三方,A 的 cookie 也就是第三方 cookie;

HTTP 1.1

  1. 长连接:通过Connection: keep-alive维持连接状态,避免 1.0 中每次发送、接收报文都要重新建立连接;
  2. 管道化:请求可以连续发送,而不必等待响应,解决了客户端队首阻塞问题,但服务器仍存在队首阻塞问题,因为 1.1 要求服务器必须按顺序响应请求;

HTTP 2.0

2.0 引入了介于 HTTP 与 TCP 之间的二进制分帧层,用于将 HTTP 报文分割成帧,基于该机制实现了以下特性:

  1. 多路复用:由于每个流的帧可以错序发送,所以一个连接上可以存在多个流,接收端根据 Stream Id 将帧组合成完整报文;
  2. 头部压缩:根据 HPACK 算法压缩首部;
  3. 服务端推送:服务端可以不必等待客户端的请求,而直接推送数据;
  4. 请求优先级:每个流可以标识优先级,以便具有依赖关系的请求可以正确按依赖顺序发送和接收;
  5. 流量控制:类似于 TCP 流量控制,将双方接收速率控制在一个合理的程度,避免资源浪费;

HPACK 算法:通过静态或动态字典将首部替换成字节更少的索引,字典中不存在的首部使用哈夫曼编码发送;

  1. 优先去静态字典中查找,内置了大量常用首部与取值;
  2. 然后去动态字典中查找,每个连接维护一个动态字典,相当于静态字典的补充;
  3. 都找不到则使用哈夫曼编码;