计算机网络 | 知识笔记
本文是我在学习计算机网络过程中记录的知识笔记,由本地 markdown 文件导入,将定期更新我新学到的知识。如果有什么错误的地方,欢迎指教。
HTTP
常见的 HTTP 请求方法
- GET:向服务器获取数据
- POST:将实体提交到指定的资源,通常会造成服务器资源的修改
- PUT:上传文件,或更新数据
- DELETE:删除服务器上的对象
- HEAD:获取报文首部,与 GET 相比,不返回报文主体部分
- OPTIONS:询问支持的请求方法,用来跨域请求
- CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行 TCP 通信
- TRACE:回显服务器收到的请求,主要用于测试或诊断
GET 和 POST 请求
应用场景:
- GET 是一个幂等的请求,请求用于对服务器资源不会产生影响的场景,如请求一个网页的资源。
- POST 不是一个幂等的请求,一般用于对服务器资源产生影响的场景,比如注册用户。
是否缓存:
- 浏览器一般会对 GET 请求缓存
- 一般不会对 POST 请求缓存
发送报文的格式:
- GET 请求的报文实体部分为空
- POST 请求报文中实体部分为数据
安全性:
- GET 请求的 query 参数存放在 url 中,由于 url 会被浏览器记录,不太安全
请求长度:
- GET 请求长度受到浏览器对 url 长度的限制
参数类型:
- POST 请求支持更多的数据类型
POST 和 PUT 请求
- PUT 请求用于更新数据,发送数据并修改数据的内容,但是不会增加数据的种类
- POST 请求用于创建数据,会改变数据的种类等资源,创建新的内容
OPTIONS 请求
用于请求获得由 Request-URI 表示的资源在请求/相应的通信过程中可以使用的功能选项。该请求方法的相应不能缓存。
通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能。
主要用途:
-
获取服务器支持的所有 HTTP 请求方法
-
用来检查访问权限
例如:在进行 CORS 跨域资源共享时,对于复杂请求,就是使用 OPTIONS 方法发送嗅探请求,以判断是否有对指定资源的访问权限。
有两种情况会自动发送 OPTIONS 请求:
- 跨域发送 POST 请求(content-type 为
www-form-urlencoded、multipart/form-data 或text/plain 则不会发送 OPTIONS) - 设置了自定义的头部字段时
OPTIONS 请求和响应的信息:
常见的 HTTP 请求头和响应头
HTTP Request Header 请求头:
- Accept:浏览器能够处理的内容类型
- Accept-Charset:浏览器能够显示的字符集
- Accept-Encoding:浏览器能够处理的压缩编码
- Accept-Language:浏览器当前设置的语言
- Authorization:身份验证信息
- Connect:浏览器与服务器之间连接的类型
- Cookie:当前页面设置的任何 Cookie
- Host:指明了请求将要发送到的服务器主机名和端口号
- Referer:发出请求的页面的URL
- User-Agent:浏览器的用户代理字符串
HTTP Responses Header 响应头:
-
Date:消息发送的时间,描述格式由 rfc822 定义
-
server:服务器名称
-
Connection:浏览器与服务器之间连接的类型
-
Cache-Control:控制 HTTP 缓存
-
Content-Type:表示后面的文档属于什么类型
-
(默认值)application/x-www-form-urlencoded:浏览器原生表单
按照 key1=val1&key2=val2 的方式编码。
- POST 将数据放在 body 里面
- GET 将数据放在 url 里
-
multipart/form-data: 常见的 POST 提交方式,通常用于上传文件
会设置一个
boundary 分割字符串 -
application/json: 服务器消息主题是序列化后的 JSON 字符串
-
text/xml: 通过 XML 格式提交数据
-
-
Set-Cookie:返回给客户端的 Cookie
keep-alive
短连接:HTTP 1.0 中默认每次请求/应答,客户端和服务器都要新建一个连接,完成后立即断开连接。
长连接:当使用 Keep-Alive 模式时,该功能使客户端到服务器端的连接持续有效,当出现对服务器端的后续请求时,Keep-Alive 功能避免了建立或者重新建立连接。
Keep-Alive 的使用方法:
-
HTTP 1.0 版本是默认没有 keep-alive 的(默认不发送),所以:
- 如果想要保持连接,必须手动配置发送
Connection: keep-alive 字段。 - 如果想要断开连接,需要发送
Connection: close 字段。
- 如果想要保持连接,必须手动配置发送
-
HTTP 1.1 规定了默认保持长连接,
-
数据传输完成了保持 TCP 连接不断开,等待在同域名下继续使用这个通道传输数据
-
如果需要关闭,则发送
Connection: close 字段。
-
Keep-Alive 的建立过程:
- 客户端向服务器端在发送请求报文时,在报文首部添加发送 Connection 字段
- 服务器收到请求并处理 Connection 字段
- 服务器回送
Connection: keep-alive 字段给客户端 - 客户端接收到该字段
- Keep-Alive 连接建立成功
服务器端自动断开过程(没有 keep-alive 时):
- 客户端向服务器只是发送内容报文,不包含 Connection 字段
- 服务器收到请求并处理
- 服务器返回客户端请求的资源并关闭连接
- 客户端接收资源,发现没有 Connection 字段,断开连接
客户端请求断开连接过程:
- 客户端向服务器发送
Connection: close 字段 - 服务器收到请求并处理 Connection 字段
- 服务器回送相应资源并断开连接
- 客户端接收资源并断开连接
开启 Keep-Alive 的优点:
- 较少的 CPU 和内存的使用(因为同时打开的连接减少了)
- 允许请求和应答的 HTTP 管线化(pipeline)
- 降低了拥塞控制(TCP 连接减少了)
- 减少了后续请求的延迟(无需再进行握手)
- 报告错误无需关闭 TCP 连接
开启 Keep-Alive 的缺点:
- 长时间的 TCP 连接容易导致系统资源无效占用,浪费系统资源
HTTP 1.0 和 HTTP 1.1 的区别
连接方面
- http 1.0 默认使用非持久连接(浏览器每次发起HTTP请求都要与服务器建立一个TCP连接,服务器完成请求处理之后就会立即断开这个 TCP 连接)
- http 1.1 默认使用持久连接,通过持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。
管道网络传输
HTTP/1.1 采用了长连接的方式,使得管道(pipeline)网络传输成为了可能。
管道网络传输是指:可以在同一个 TCP 连接中,客户端发送多个请求,只要第一个请求发送出去,不必等待其响应,就可以发送第二个请求,以此减少整体的响应时间。
但是服务器还是按照顺序回应请求,如果前面的回应特别慢,后面就会有许多请求排队,造成队头阻塞。
队头阻塞
HTTP 传输的报文必须是一发一收,任务队列的串行执行可能会因为队首阻塞后面的请求。
队头阻塞的解决方案
(1)并发连接:对于一个域名允许分配多个长连接,相当于增加了任务队列
(2)域名分片:将域名分出许多二级域名,它们都指向同样的一台服务器,能够并发的长连接数变多。
资源请求方面
-
http 1.0 存在浪费带宽的现象:
- 客户端只是需要某个对象的一部分,而服务器将整个对象发送过来
- 不支持断点续传
-
http 1.1 在请求头引入了 range 头域,它允许只请求资源的某个部分,返回码是 206(Partial Content),这样就方便开发者自由的选择以充分利用带宽和连接
缓存方面
- http 1.0 主要使用 header 里面的 If-Modified-Since、Expires 来作为缓存判断的标准
- http 1.1 引入更多的缓存控制策略,如 Etag、If-Unmodified-Since、If-Match、If-None-Match
其他方面
- http 1.1 中新增了 host 字段,用来指定服务器的域名。http 1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此请求消息中 URL 并没有传递主机名。但随着虚拟主机计数的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享了一个 IP 地址。因此有了 host 字段,就可以将请求发往同一台服务器的不同网站。
- http 1.1 新增了很多请求方法:PUT、DELETE、OPTIONS、TRACE、CONNECT 等
HTTP 1.1 和 HTTP 2.0
协议内容
- 在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。
- HTTP/2 是一个彻底的二进制协议,头信息和数据体都是二进制的,统称为“帧”,可以分为头信息帧和数据帧。帧的概念是实现多路复用的基础。
多路复用
HTTP/2 实现了多路复用,HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按顺序一一发送,这样就避免了“队头堵塞”的问题。
数据流
HTTP/2 使用了数据流的概念,因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须对数据包进行标记,指出它属于哪个请求。
HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号,数据包发送时,都必须标记数据流 ID,用来区分它属于哪个数据流。
头信息压缩
-
由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息,因此很多请求的字段可能重复,比如 Cookie 和 User-Agent,浪费带宽也影响速度。
-
HTTP/2 实现了头信息压缩
- 头信息使用 gzip 或 compress 压缩后再发送
- 客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样的字段了,只发送索引号,这样可以提高速度
服务器推送
HTTP/2 允许服务器未经请求,主动向客户端发送资源,即服务器推送。
使用服务器推送提前给客户端推送必要的资源,相对减少一些延迟时间。
注意:主动推送的是静态资源,不同于 WebSocket 以及使用 SSE 等方式向客户端发送即使数据的推送。
HTTP 加载页面多张图片
- HTTP 1 下,浏览器对一个域名的最大 TCP 连接数为 6,所以会请求多次
可以使用 多域名部署 解决,提高同时请求的数目,加快页面图片的获取速度。
- HTTP 2 下,由于支持多路复用(在一个 TCP 连接中发送多个 HTTP 请求)可以一瞬间加载很多资源。
HTTP 2 的头部压缩算法
HTTP 2 的头部压缩是 HPACK 算法。
在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,采用哈夫曼编码来压缩整数和字符串,可以达到 50%~90% 的高压缩率。
具体来说:
-
客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送
-
首部表再 HTTP/2 的连续存续期内始终存在,由客户端和服务器共同渐进地更新
-
每个新的首部键值对:
- 要么被追加到当前表的末尾
- 要么替换表中之前的值
例如下图中的两个请求,请求一 发送了所有的头部字段,请求二 只需要发送差异数据,这样可以减少冗余数据,降低开销。
HTTP 的请求报文
HTTP 的请求报文有 4 部分:
- 请求行
- 请求头部
- 空行
- 请求体
其中:
- 请求行
包括:请求方法字段、URL 字段、HTTP 协议版本字段,分别用空格分割
例如 GET /index.html HTTP/1.1
- 请求头部
请求头部由关键字/值对组成,每行一对,键值之间用英文逗号 : 分割
- User-Agent:产生请求的代理类型
- Accept:客户端可识别的内容列表
- Host:请求的主机名,允许多个域名同处一个 IP地址,即虚拟主机
- 请求体
POST,PUT 等请求携带的数据
HTTP 响应报文
HTTP 响应报文有 4 部分:
- 响应行
- 响应头
- 空行
- 响应体
- 响应行
包括:网络协议版本,状态码和状态码的原因短语,用空格分割
例如:HTTP/1.1 200 OK
- 响应头:响应头部组成
- 响应体:服务器响应的数据
HTTP 协议的优点和缺点
HTTP 是超文本传输协议,它定义了客户端和服务器之间交换报文的格式和方式,默认使用 80 端口。
它使用 TCP 作为传输层协议,保证了数据传输的可靠性。
HTTP 协议的优点
-
支持客户端/服务器模式
-
简单快速
- 客户端向服务器发送请求时,只需传送请求方法和路径。
- 由于 HTTP 协议简单,服务器的程序规模较小,因而通信速度快
-
无连接
- 限制每次连接只处理一个请求
- 服务器处理完客户端的请求,并收到客户的应答后,立即断开连接,节省传输时间
-
无状态
- HTTP 协议是无状态协议,状态指通信过程的上下文信息
- 缺少状态意味着如果后续需要处理前面的信息,必须重传,导致每次连接传送的数据量增大
- 另一方面,在服务器不需要先前信息时,它的应答就更快
-
灵活
- HTTP 允许传输任意类型的数据对象,正在传输的类型由
Content-Type 标记
- HTTP 允许传输任意类型的数据对象,正在传输的类型由
HTTP 协议具有以下缺点:
-
**无状态:**HTTP 服务器不会保存关于客户的任何信息(可以用 cookie 技术解决)
-
不安全:
- 协议报文是文本形式,直接暴露给外界,不安全
- 不验证通信方的身份,因此可能遭遇伪装
- 无法证明报文的完整性,有可能遭到篡改
HTTP 3.0
HTTP/3 基于 UDP 协议实现了类似于 TCP 的多路复用数据流、传输可靠性等功能,这套功能被称为 QUIC (Quick UDP Internet Connection)协议。
- 流量控制、传输可靠性功能
QUIC 在 UDP 的基础上增加了一层来保证数据传输可靠性。
它提供了数据包重传、拥塞控制,以及其他一些 TCP 中的特性。
- 集成 TLS 加密功能
目前 QUIC 使用 TLS 1.3,减少握手所花费的 RTT 数
- 多路复用
同一物理连接上可以由多个独立的逻辑数据流,实现了数据流的单向传输,解决了 TCP 的队头阻塞问题。
4. 快速握手
基于 UDP,可以实现 0~1 个 RTT 来建立连接
URL 的组成
Scheme:协议
Domain Name:域名
Port:端口号
Path to the file:文件路径
-
/path/to/ 虚拟目录 -
myfile.html 文件名
Parameters:参数
Anchor:锚部分
HTTP 的缓存
HTTP 缓存主要是通过请求和响应报文头中的对应的 Header 信息,来控制缓存的策略。
HTTP 缓存可以缩短网页请求资源的举例,减少延迟,节省网络流量,并且由于缓存文件可以重复利用,因此还能降低网络符合,提高客户端响应。
根据是否需要重新向服务器发起请求,可分为:
- (不需要发送请求)强缓存
- (需要发送请求)协商缓存
强缓存
也叫强制缓存
当命中强缓存时,客户端不会再请求服务器,直接从缓存中读取内容,并返回 HTTP 状态码 200。
强制缓存,在响应头由 Expires、Cache-Control 和 Pragma 控制。
优先级:Pragma > Cache-Control > Expires
Expires
HTTP1.0 的属性,值为服务器返回的过期时间。
浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。
缺点是如果服务端和客户端时间不一致,会导致命中误差。
Cache-Control
HTTP1.1 属性,有以下常用的属性:
-
no-store:禁止缓存
-
no-cache:不使用强缓存,每次需向服务器验证缓存是否失效
-
private/public:
- private 指单个用户
- public 指可以被任何中间人(代理服务器、CDN等)缓存
-
max-age:距离请求发起的时间的秒数
-
must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证
Cache-Control: max-age=0 和 no-cache 有什么不同?
语义上不同。
max-age=0 告诉客户端资源的缓存到期,应该向服务器验证缓存的有效性。
no-cache 告诉客户端使用缓存前必须向服务器验证缓存的有效性。
max-age 和 expires 共存时,max-age 的优先级更高。
pragma
HTTP1.0 属性,那时候 Cache-Control 还没有出来
效果和 cahe-control 的 no-cache 一致。
强缓存的资源存储位置
| 状态 | Network - Size | 含义 |
|---|---|---|
| 200 | from memory cache | 不请求网络资源,资源在内存。一般是脚本、字体、图片,浏览器关闭,数据将被释放 |
| 200 | from disk cache | 请求网络资源,资源在磁盘。一般是 css 等,关闭浏览器数据还存在。 |
| 200 | 资源大小 | 从服务器下载最新资源 |
| 304 | 报文大小 | 请求服务端发现资源未更新,使用本地资源 |
为什么同一个资源有时是 from memory cache,有时是 from disk cache?
Chrom 会根据本地内存的使用率来决定缓存存放位置,如果内存使用率很高,则会放在磁盘中。
协商缓存
向服务器发送请求,服务器会根据请求头的一些参数来判断是否命中协商缓存。
如果命中,则返回 304 状态码并带上新的响应头通知浏览器从缓存中读取资源。
协商缓存,响应头中有两个字段标记规则
Last-Modified / If-Modified-Since
-
Last-Modified:(浏览器第一个请求资源,服务器响应头字段)值是资源文件最后一次更改时间,精确到秒 - 下一次发送请求时,请求头里的
If-Modified-Since 就是之前的 Last-Modified - 服务器根据最后修改时间判断命中,如果命中,状态码为304且不返回资源,不返回 Last-Modified
Etag / If-None-Match
Etag 的校验优先级高于 Last-Modified
-
Etag 是加载资源时,服务器返回的响应头字段。值是对资源的唯一标记,一个 hash 码 - 浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到请求头里的
If-None-Match - 服务器接收到 If-None-Match 的值后,会拿来根资源文件的 Etag 值作比较,如果相同,则表示资源文件没有发生改变,命中协商缓存。
在精确度上,Etag 要优于 Last-Modified,因为后者的精确度是秒,而前者是资源的哈希值。
在性能上,Etag 要劣于 Last-Modified,因为需要通过算法计算 hash 值。
在优先级上,服务器校验优先考虑 Etag。
用户行为对缓存的影响
| 用户操作 | Expires/Cache-Control | Last-Modified/Etag |
|---|---|---|
| 地址栏回车、页面链接跳转、新开窗口、前进后退 | 有效 | 有效 |
| F5刷新 | 无效 | 有效 |
| Ctrl + F5 强制刷新 | 无效 | 无效 |
勘误:图中协商缓存命中后返回的状态码应该是 304 而不是 302
不设置缓存的方法
- 在 html 文件设置 meta
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate">
<meta http-equiv="expires" content="Wed, 26 Feb 1997 00:00:00 GMT">
- 服务端响应添加
Cache-Control: no-cache, must-revalidate - 修改请求头
If-modified-since:0 或If-none-match - 请求 url 后增加时间戳
- 服务端设置
Cache-Control: private 指令,防止代理服务器缓存资源
304 状态码
服务器为了提高网络访问速度,对之前访问的部分网页指定缓存机制,当客户端再次对这些网页进行请求,服务器会根据缓存内容判断页面与之前是否相同,若相同便直接返回304,此时客户端调用缓存内容,不必进行二次下载。
状态码 304 不应该认为是一种错误,而是对客户端由缓存情况下服务端的一种响应。
搜索引擎爬虫会更加青睐内容源频繁更新的网站。通过特定时间内对网站抓取返回的状态码来调节对该网站的抓取频次。
- 若网站在一定时间内一直处于304状态,那么爬虫可能会降低对微网站的抓取次数。
- 反之,则每次抓取都能获取新内容,日积月累,回访率也会提高
产生较多 304 状态码的原因:
- 页面更新周期长或不更新
- 纯静态页面或强制生成静态html
304 状态码出现过多会造成以下问题:
- 网站快照停止
- 收录减少
- 权重下降
HTTPS
HTTP 和 HTTPS 协议的区别
-
HTTPS 协议需要 CA 证书,费用较高;HTTP 协议不需要
-
HTTP 协议是超文本传输协议,信息是明文传输的,HTTPS 是具有安全性的 SSL 加密传输协议
- HTTP 不验证对方的身份,可能被劫持、伪装,无法证明报文的完整性(可能被篡改)
-
使用不同的连接方式,端口也不同,HTTP 协议默认端口是80,HTTPS 协议默认端口是 443
-
HTTP 协议连接很简单,是无状态的;HTTPS 协议是具有 SSL 和 HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 更加安全
HTTPS 是如何保证安全的
对称加密:通信的双方使用同一个密钥进行加解密
- 简单,性能好
- 无法解决首次把密钥发送给对方的问题,很容易被黑客拦截密钥
非对称加密:
- 使用私钥和公钥,组成密钥对
- 用私钥加密的数据,只有对应的公钥才能解密;用公钥加密的数据,只有对应的私钥才能解密
- 通信双方手里都有一套自己的密钥对,通信之前双方会把自己的公钥发送给对方
- 然后用收到的公钥加密数据,响应给对方,等到了对方那里,对方用自己的私钥进行解密
- 安全性能更高,但是速度比较慢,影响性能
HTTPS 使用非对称加密,但是这样存在中间人问题:
如果此时再客户端和服务器之间存在一个中间人,这个人把双方通信互发的公钥换成自己的公钥,则中间人可以轻松解密通信双方所发送的所有数据。
因此需要一个安全的第三方颁发证书(CA),证明双方的身份,防止被中间人攻击。
证书中包括:签发者、证书用途、使用者公钥、使用者私钥、使用者的 HASH 算法、证书到期时间
此时又有新的问题:如果中间人篡改了证书,那么身份证明就无效了,这个时候需要一个新的技术——数字签名。
数字签名就是用 CA 自带的 HASH 算法对证书的内容进行 HASH 得到一个摘要,再用 CA 的密钥加密,最终组成数字签名。当别人把他的证书发过来时,需要用同样的 HASH 算法,再次生成消息摘要,然后用 CA 的公钥对数字签名解密,得到 CA 创建的消息摘要,两者一比较即可知道中间有没有被人篡改。
TLS/SSL的工作原理
TLS / SSL 全程安全传输层协议(Trasnport Layer Security),是介于 TCP 和 HTTP 之间的一层安全协议,不影响原有的 TCP 协议和 HTTP 协议。
SSL 是 TLS 的前身,TLS 弥补了一些安全性和性能方面的缺点,现在广泛使用的是 TLS
采用非对称加密与服务器进行通信,实现身份的验证并协商对称加密使用的密钥。
对称加密算法采用协商密钥对信息以及信息摘要进行加密通信,不同节点之间采用的对称密钥不同,从而保证信息只能通信双方获取。
TLS / SSL 的功能实现主要依赖三类基本算法:
- 散列函数 hash:验证信息完整性
- 对称加密算法:采用协商的密钥对数据加密
- 非对称加密:实现身份认证和密钥协商
- 散列函数 hash
常见的有 MD5,SHA1,SHA256,这些函数的特点是单向不可逆,对输入数据非常敏感,输出的长度固定,任何数据的修改都会改变散列函数的结果,可以用于防止信息篡改并验证数据的完整性。
特点:在信息传输过程中,单靠散列函数不能防止信息篡改,由于传输是明文的,中间人可以篡改信息后重新计算信息的摘要,所以需要对传输的信息和信息摘要进行加密。
- 对称加密
对称加密的方法是,双方使用同一个密钥对数据进行加密和解密。
如果和保证密钥传输的安全性?需要用非对称加密的方法协商对称加密的密钥。
特点:对称加密的优势就是信息传输使用一对一,需要共享相同的密码,密码的安全是保证信息安全的基础,服务器和 N 个客户端通信,需要维持 N 个密码记录且不能修改密码。
- 非对称加密
拥有一个密钥对,公钥是公开的,私钥是保密的。
用公钥加密的数据,只能用私钥解密。
用私钥加密的数据,只能用公钥解密。
我们可以将公钥发布出去,任何想和我们通信的客户,都可以使用我们提供的公钥对数据进行加密,这样我们就可以使用私钥解密。缺点是加密的过程很慢,如果每次通信都是用非对称加密的方式的话,等待时间会太长。
服务端可以实现一对多的通信,因为掌握公钥不同的客户端之间不能相互解密信息。
总结:
TLS / SSL 的工作方式就是客户端使用非对称加密与服务器进行通信,实现身份验证并协商对称加密使用的密钥。
对称加密算法采用协商密钥对信息以及信息摘要进行加密通信,不同节点之间采用的对称密钥不同,从而保证信息只能通信双方获取。
HTTPS 四次握手
(1)首先由客户端向服务器端发送
- 使用的协议的版本号
- 一个随机数
- 可以使用的加密方法
(2)服务器收到后,确认加密方法,向客户端发送
- 选择使用加密方法
- 一个随机数
- 自己的数字证书
(3)客户端收到后,首先检查数字证书是否有效,如果有效
- 生成一个随机数,使用证书中的公钥对随机数进行加密
- 还会生成一个前面所有内容的 hash 以供检验,然后发送给服务端
(4)服务器端收到后
- 使用自己的私钥解密
- 向客户端发送一个前面所有内容的 hash 以供检验
这时候,双方都有三个随机数,按照之前约定的加密方法,使用这三个随机数生成一把密钥,以后双方通信前,就是用这个密钥对数据进行加密后再传输。
详情见 稀土掘金 - HTTPS握手
HTTP 状态码
常用状态码简介
-
2XX 成功
- 200 OK,表示从客户端发来的请求再服务器端被正确处理
- 204 No Content,表示请求成功,但响应报文不含实体的主体部分
- 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于 要求请求方重置内容
- 206 Partial Content,表示进行了范围请求
-
3XX 重定向
- 301 moved permanently,永久性重定向,表示资源已被分配到新的 URL
- 302 found,临时重定向,表示资源临时被分配了新的 URL
- 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源
- 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
- 307 temporary redirect,临时重定向,和 302 含义类似,但是期望客户端保持请求方法不变向新的地址发送请求
-
4XX 客户端错误
- 400 bad request,请求报文存在语法错误
- 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
- 403 forbidden,表示对请求资源的访问被服务器拒绝
- 404 not found,表示在服务器上没有找到请求的资源
-
5XX 服务器错误
- 500 internal server error,表示服务器在执行请求时发生了错误
- 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能
- 503 service unavailable,表示服务器暂时处于超负载或正在停机维护,无法处理请求
同样是重定向,307,303,302 的区别?
-
302 是 http 1.0 的协议状态码
-
http 1.1 将302细化为:
- 303:明确表示客户应当采用 get 方法获取资源,他会把 POST 请求变为 GET 请求重新定向
- 307:会遵照浏览器标准,不会从 post 变为 get
DNS
DNS 同时使用 TCP 和 UDP 协议
DNS 占用 53 号端口,同时使用 TCP 和 UDP 协议。
- 在区域传输的时候使用 TCP 协议
辅域名服务器会定时(一般为3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行以此区域传送,进行数据同步。
区域传送使用 TCP 而不是 UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。
TCP 的可靠连接,保证了数据的准确性。
- 在域名解析的时候使用 UDP 协议
客户端向 DNS 服务器查询域名,一般返回的内容都不超过 512 字节,用 UDP 传输即可。
不用经过三次握手,这样 DNS 服务器负载更低,响应更快。
理论上说客户端也可以指定向 DNS 服务器查询时使用 TCP,但事实上,很多 DNS 服务器进行配置的时候,仅支持 UDP 查询包。
DNS 完整的查询过程
- 首先会在浏览器的缓存、操作系统缓存、Host 文件缓存中查找对应的 IP 地址,如果查找到直接返回,否则进行下一步
- 将请求发送给本地 DNS 服务器,在本地域名服务器缓存中查询,如果查找到,则直接返回,否则进行下一步
- 本地 DNS 服务器向根域名服务器发送请求,如果找到则直接返回,否则根域名服务器会返回一个所查询域的顶级域名服务器地址,进行下一步
- 本地 DNS 服务器向顶级域名服务器发送请求,接收请求的服务器查询自己的缓存,如果有记录,就返回查询结果,否则返回相关的下一级的权威域名服务器的地址
- 本地 DNS 服务器向权威域名服务器发送请求,域名服务器返回对应的结果
- 收到结果后,本地 DNS 服务器将返回结果保存在缓存中,便于下一次使用
- 本地 DNS 服务器将返回结果返回给浏览器
比如查询 www.baidu.com 的 IP 地址:
- 查看缓存是否有域名的 IP 地址
- 向本地 DNS 服务器请求
- 向根域名服务器请求,查询不到则返回 .com 顶级域名服务器的地址
- 向 .com 顶级域名服务器发送请求,查询不到则返回 .baidu 权威域名服务器的地址
- 向 .baidu 权威域名服务器发送请求
- 本地 DNS 服务器缓存结果
- 本地 DNS 服务器将结果返回给浏览器
CDN
功能:
- 加速访问
- 负载均衡
引入 CDN 后,DNS 解析时步骤发生改变。
原来是最后向权威域名服务器查询,现在改为:
向智能 DNS 发送请求,它会在 CDN 网络内寻找何时的节点返回,然后客户端再向这个节点请求获取数据。
网络模型
OSI 七层模型
ISO(国际标准化组织)为了更好的使网络应用得到普及,推出了 OSI 参考模型。
- 应用层
最靠近用户的一层,为计算机用户提供应用接口,也为用户直接提供各种网络服务。应用层常见的网络服务协议有:HTTP,HTTPS,FTP,POP3,SMTP,DNS 等。
- 客户端与服务器经常会有数据请求,这个时候就是用 HTTP(Hyper Text Transfer Protocol,超文本传输协议)或者 HTTPS。
- FTP 是文件传输协议
- SMTP 简单邮件传输协议
- 表示层
表示层提供各种用于应用层数据的编码和转换功能(如JPEG、MP3),确保一个系统的应用层发送的数据能被另一个系统的应用层识别。
如果必要,该层提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准形式。数据压缩和加密也是表示层可提供的转换功能之一。
在项目开发中,为了方便数据传输,可以使用 base64 对数据进行编码/解码。如果按功能来划分,base64 应该是工作在表示层。
- 会话层
会话层及以上传输的都是报文。
会话层负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。
checkpoint(检查点) 机制来维持可靠会话。
检查点定义了一个最接近成功通信的点,并且定义了当发生内容丢失或损坏时需要回滚以便恢复丢失或损坏数据的点,即断点下载的原理。
- 传输层
定义端口号、控流、校验,这一层传输数据包(TCP 和 UDP 各自的叫法不同)。
传输层建立了主机端到端的链接,传输层的作用是为上层协议通过端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。
该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是:
- 在两个传输实体间的一条主机到主机的
- 可由用户控制和设定的
- 可靠的
数据通路。
我们常说的 TCP、UDP 工作在这一层。端口号就是这里的端。
详细请看本文其他位置。
- 网络层
这一层传输数据包。
网络层通过 IP 寻址建立两个节点之间的连接,为源端的传输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的传输层。
IP 协议是 Internet 的基础。
可以这样理解:传输层规定了数据包的传输方式,而网络层规定了数据包的传输路线。
网络层的功能:
-
寻址:根据 IP 来标识互联网上的设备
-
路由:通过路由器的路由功能,找到传输的路线
- 数据链路层
将比特组合成字节,再将字节组合成**帧**(拼装了 MAC 地址),使用链路层地址(以太网使用 MAC 地址,每个网卡的唯一标识)来访问介质,并进行差错检测。
网卡在局域网中以广播的方式进行数据传输。
可以这样理解:网络层规划了数据包的传输路线,而数据链路层就是传输路线,不过数据链路层在此基础上还增加了差错控制的功能。
- 物理层
实际最终信号的传输是通过物理层实现的,使用物理介质传输比特流。
规定了电平、速度和电缆针脚。
常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。
OSI 七层模型通信特点:对等通信
为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信。每一层通信过程中,使用本层自己协议进行通信。
TCP/IP 五层协议
TCP/IP 五层协议和 OSI 七层协议对应关系如下:
- 应用层
直接为应用程序提供服务。
应用层协议定义的是应用程序间通信和交互的规则,不同的应用有着不同的应用层协议,比如:
- HTTP(万维网服务)
- FTP(文件传输)
- SMTP(电子邮件)
- DNS(域名查询)
- 传输层
也叫运输层
负责为两台主机中的进程提供通信服务
-
TCP(Transmission Control Protocol,传输控制协议):
提供面向连接的、可靠的数据传输服务,数据传输的基本单位是报文段(segment)
-
UDP(User Datagram Protocol,用户数据报协议):
提供无连接的、尽最大努力的数据传输服务,但不保证数据传输的可靠性,数据传输的基本单位是用户数据报(user datagram)
- 网络层
也叫网际层
负责两台主机之间的通信服务,通过选择合适的路由将数据传递到目标主机。
- 数据链路层
负责将网络层浇下来的 IP 数据报封装成帧,并在链路的两个相邻节点间传送帧,每一帧都包含数据和必要的控制信息(如同步信息、地址信息、差错控制等)
- 物理层
确保数据可以在各种物理媒介上进行传输,为数据的传输提供可靠的环境。
TCP/IP 模型把 OSI 模型中的 应用层/表示层/会话层全部整合为 应用层
TCP/IP 五层协议也是对等通信。
各层工作者:
各层协议:
TCP 与 UDP
TCP 和 UDP的概念及其特点
TCP 和 UDP 都是传输层协议,它们都属于 TCP/IP 协议族
TCP
传输控制协议。
特点:
- 面向连接
需要进行三次握手,建立可靠的连接
- 仅支持单播传输
每条 TCP 传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播。
- 面向字节流
并不是一个一个报文地传输,而是不保留报文边界,以字节流方式进行传输。
发送的数据是报文段 segment。
- 可靠传输
TCP 具有段编号和确认号。
TCP 为了保证报文传输的可靠,给每一个包一个序号,同时序号保证了传送到接收端实体的包的按序接收。
然后接收端实体会对已成功接收的字节发回一个相应的确认(ACK);
如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(被假设为丢失了)将会被重传。
重传机制:
基于时间和确认信息
TCP 在发送一个数据之后,就开启一个定时器,若在这个事件内没有收到发送数据的 ACK 取人报文,则对该报文进行重传,在达到一定次数还没有成功时,放弃并发送一个复位信号。
- 提供拥塞控制
当网络出现拥塞的时候,TCP 能够减小向网络注入数据的速率和数量,缓解拥塞。
6。 提供全双工通信
TCP 允许双方在任何时候都发送数据,因为 TCP 连接的两端都设有缓存,用来临时存放双向通信的数据。
当然,TCP 可以立即发送一个数据段,也可以缓存一段时间以一次发送更多的数据段(最大的数据段大小取决于 MSS)
UDP
用户数据报协议。
特点如下:
- 面向无连接
UDP 并不需要像 TCP 一样在发送数据前进行三次握手建立连接,并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。
具体来说是:
- 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头作为标识,然后就传递给网络层了
- 在接收端,网络层将数据传递给传输层,UDP 只取出 IP 报文头,就传递给应用层,不会有任何拼接操作
- 有单播,多播,广播的功能
UDP 支持一对一,一对多,多对多的传输方式
- 面向报文
UDP 直接对应用程序交下来的报文添加 UDP 首部后直接交付给 IP 层,既不合并,也不拆分,而是保留这些报文的边界。
发送的数据是 用户数据报 user datagram。
因此,应用程序必须选择合适大小的报文。
- 不可靠性
无连接:UDP 在传输数据之前不需要先建立连接,目的主机的运输层在接收到 UDP 报文后,不需要确认,提供不可靠交付。
- 不保证消息交付:不确认,不重传,无超时,不进行数据备份
- 不保证交付顺序:不设置包序号,不重拍,不会发生队首阻塞
- 不跟踪连接状态:不必建立连接或重启状态机
- 不进行拥塞控制:不内置客户端或网络反馈机制,在网络条件差时容易丢包
- 头部开销小,传输数据报文时高效
UDP 头部包含了以下几个数据:
- 两个十六位的端口号,分别为源端口(可选字段)和目标端口
- 整个数据报文的长度
- 整个数据报文的检验和(IPv4 可选字段),用于发现头部信息和数据中的错误
UDP 的头部开销小,只有 8 个字节;而 TCP 至少需要 20 字节。
TCP 和 UDP 的应用场景
TCP 应用场景
效率要求相对低,但对准确性要求相对高的场景:
- 文件传输
- 接收邮件
- 远程登录
因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有 UDP 高。
UDP 应用场景
效率要求相对高,对准确性要求相对低的场景:
- QQ 聊天
- 在线视频
- 网络语音通话
- 广播通信
TCP 和 UDP 的区别
| UDP | TCP | |
|---|---|---|
| 是否连接 | 无连接 | 面向连接 |
| 是否可靠 | 不可靠,无流量控制和拥塞控制 | 可靠传输 |
| 连接对象个数 | 单播,多播,广播 | 单播 |
| 传输方式 | 面向报文 | 面向字节流 |
| 首部开销 | 8字节 | 至少20字节 |
| 使用场景 | 实时应用,如视频会议和直播 | 可靠性要求高的应用,如文件传输 |
TCP 的拥塞控制和流量控制
拥塞控制
拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况。
TCP 的拥塞控制机制主要是以下四种机制:
- 慢启动(慢开始)
- 拥塞避免
- 快速重传
- 快速恢复
(1)慢启动
-
在开始发送时,设置 cwnd = 1(cwnd 是拥塞窗口)
-
思路:开始的时候不要发送大量数据,而是先测试一下网络的拥塞程度,由小到大增加拥塞窗口的大小。
-
为了防止 cwnd 增长过大引起网络拥塞,设置一个慢开始门限(ssthresh 状态变量)
- 当 cnwd < ssthresh:使用慢开始算法
- 当 cnwd = ssthresh:使用慢开始算法或拥塞避免算法
- 当 cnwd > ssthresh:使用拥塞避免算法
(2)拥塞避免
- 拥塞避免未必能够完全避免拥塞,是在拥塞避免阶段将拥塞窗口控制为按线性增长,使网络不容易出现阻塞
- 思路:让 cwnd 缓慢地增大,每经过一个返回时间 RTT 就把发送方的拥塞控制窗口加一
- 无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞,就把慢开始门限 ssthresh 设置为出现拥塞时发送窗口大小的一半,然后把拥塞窗口设置为 1,执行慢开始算法。
判断网络出现拥塞的根据是没有收到确认,尽管这可能是其他的原因造成的分组丢失。
(3)快速重传
- 快速重传要求接收方在收到一个失序的报文段后,立即发出重复确认(ACK = 上一个顺序接收数据的序号+1,为的是使发送方及早知道有报文段没有到达对方)。发送方只要连续收到三个重复确认就立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。
- 由于不需要等待设置的重传计时器到其,能尽早重传未被确认的报文段,从而提高整个网络的吞吐量。
(4)快速恢复
- 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把 ssthresh 减半,但是接下去并不执行慢开始算法。
- 考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。
- 因此此时将 cwnd 设置为 ssthresh 的大小,然后执行拥塞避免算法(cwnd 线性增长)。
流量控制
流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。
TCP 采用大小可变的滑动窗口来进行流量控制,窗口大小的单位是字节,即每次传输的数据大小。
- 当一个连接建立时,连接的每一端分配一个缓冲区来保存输入的数据,并将缓冲区的大小发送给另一端。
- 当数据到达时,接收方发送确认,其中包含了自己剩余的缓冲区大小(剩余的缓冲区空间的大小被称为窗口,指出窗口大小的通知被称为窗口通告。接收方在发送确认时都含有一个窗口通告)
- 如果接收方应用程序读数据的速度能够和数据到达的速度一样快,接收方将在每一确认中发送一个正的窗口通告。
- 如果发送方操作的速度快于接收方,接收到的数据最终将充满接收方的缓冲区,导致接收方通告一个零窗口。发送方收到一个零窗口通告时,必须停止发送,直到接收方重新通告一个正的窗口。
TCP 的可靠传输机制
TCP 的可靠传输机制是基于连续 ARQ 协议和滑动窗口协议的。
TCP 协议在发送方维持了一个发送窗口。
- 发送窗口以前的报文段是已经发送并确认了的报文段
- 发送窗口以后的报文段是缓存中还不允许发送的报文段
当 发送方 向 接收方 发送报文时,会依次发送窗口内的所有报文段,并且设置一个定时器,这个定时器可以理解为是最早发送但未收到确认的报文段。
如果在定时器的时间内收到某一个报文段的确认回答,则滑动窗口,将窗口的首部向后移动到确认报文段的后一个位置。
- 此时如果还有已发送但没有确认的报文段,则重新设置定时器。
- 如果没有,则关闭定时器。
如果定时器超时,则重新发送所有已发但未收到确认的报文段,并将定时器的超时时间设置为之前的两倍。
如果发送方收到三个冗余的确认应答,说明该报文段以后的某个报文段很可能丢失了,那么发送方会启用快速重传机制,就是在定时器结束之前,发送所有已发但未收到确认的报文段。
接收方使用的是累计确认的机制。
对于所有按序到达的报文段,接收方返回一个报文段的肯定回答。
如果收到了一个乱序的报文段,那么接收方会直接丢弃,并返回一个最近的按需到达的报文段的肯定回答。
使用累计确认保证了返回的确认号之前的报文段都已经按序到达了,所以发送方的发送窗口可以移动到已确认报文段后面。
发送窗口的大小是变化的,它是由接收窗口剩余大小和网络中拥塞程度来决定的,TCP 就是通过控制发送窗口的长度来控制报文段的发送速率。
但是 TCP 协议并不完全和滑动窗口协议相同,因为许多的 TCP 实现会将失序的报文段给缓存起来,并且发送重传时,只会重传一个报文段,因此 TCP 协议的可靠传输机制更像是滑动窗口协议和选择重传协议的一个混合体。
TCP 3次握手
三次握手:建立一个 TCP 连接需要客户端和服务器总共发送 3 个包。
- 第一次握手:
客户端处于 Closed 状态,服务器处于 Listen 状态
- 客户端发出特殊报文,该报文段没有应用层数据,但在首部的SYN 比特设置为1,且随机选择一个初始序号(client_isn)seq = x。
此时客户端处于 SYN_SEND 状态
- 发送方:我发送的数据 从X序号(随机选择的)开始
- 第二次握手:服务器收到含有SYN的报文,分配TCP缓存和变量,并向该客户TCP发送允许连接的报文段,其中也有SYN设置为1,client_isn+1,自己的初始序号(server_isn) seq = y。这个报文段称为SYNACK报文段
此时服务器处于 SYN_REVD 状态
- 接收方:我收到从X序号开始(ACK=X+1)
- 接收方:我发送的数据 从Y序号开始
- 第三次握手:客户端收到SYNACK报文段,分配TCP缓存和变量,在发送另一个报文段,附带server_isn+1,但SYN设置为0
此时客户端处于 ESTABLISHED 状态,当服务器也收到 ACK 报文后,也会变成 ESTABLISHED 状态。这样双方就建立起了连接。
- 发送方:我收到从Y序号开始(ACK=Y+1)
- 第三次交互通常承载有效载荷,即附带数据一起发送
2次握手的失败场景:
- 建立了半连接(只有服务器在维护连接,占用资源)
由于发送方第一次请求的确认延时到来,导致提前发送了第二次请求,这个请求会让接收方建立连接,但由于发送方收到了第一次请求的确认,发送方不再维护第二次请求的连接,所以在该连接上之后没有其他动作,于是接收方维护了一条无效的连接。
- 老数据的重发被当作半连接的数据接收
在半连接(假设为连接2)的情况下,发送方在连接1上发送P0数据,但还未收到数据接收确认,于是重新发送P0,该P0被当作连接2的数据接收了(请求连接建立的重发和P0的重发构成了一个新的无效连接)
TCP 4次挥手
刚开始双方都处于 ESTABLISHED 状态,假如是客户端发起关闭请求。四次挥手的过程如下:
- 第一次挥手:客户端发送一个 FIN 报文,报文中会只指定一个序列号。此时 客户端处于 FIN-WAIT-1 状态
即发出连续释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭 TCP 连接,进入 FIN_WAIT1(终止等待1)状态,等待服务端的确认。
- 第二次回收:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态
即服务端收到连续释放报文段后,发出确认报文段(ACK=1,确认号ACK=u+1,序号seq=v),服务端进入 CLOSE_WAIT (关闭等待)状态,此时的 TCP 处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入 FIN_WAIT2(终止等待2)状态,等待服务端发出的连续释放报文段。
- 第三次挥手:如果服务器也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务器处于 LAST_ACK 状态
即服务器没有要向客户端发出的数据,服务端发出连续释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务器进入 LAST_ACK(最后确认)状态,等待客户端的确认
- 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己的 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,也关闭连接,处于 CLOSED 状态
即客户端收到服务端的连续释放报文段后,对此发出确认报文段(ACK=1,seq=u+1, ack=w+1),客户端进入 TIME_WAIT(时间等待 状态,此时 TCP 未释放掉,需要经过时间等待计时器设置的时间 2MSL(最长报文段寿命,RFC793 建议为 2 分钟) 后,客户端才进入 CLOSED 状态。
之所以要等待,是怕客户端的第四次挥手的包丢失,导致服务器一直无法关闭,服务器经过一段时间没有收到,则会重新发送第三次挥手,之后客户端就会重新发送第四次挥手。
浏览器输入网址并回车之后发生了什么
- 解析URL
分析所需要使用的传输协议和请求的资源路径。
- 如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎
- 如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果有,则需要进行转义
- 缓存判断
浏览器会判断所请求的资源是否在缓存里。
- 如果有缓存且未失效,则直接使用
- 如果没有,则向服务器发起新的请求
- DNS 解析
需要获取输入的 URL 中的域名的 IP 地址。
首先判断本地是否有该域名的 IP 地址,如果有则使用;
如果没有,则向本地 DNS 服务器发起请求,看看是否有缓存;
如果没有,则本地域名服务器代我们向其他域名服务器发起请求:
向根域名服务器发起请求,看看是否有缓存;
如果没有,则向顶级域名服务器发起请求,看看是否有缓存;
如果没有,则向权威域名服务器发起请求,最终得到 IP 地址。
本地 DNS 将这个 IP 地址缓存并返回给用户。
用户向本地 DNS 服务器发起请求属于递归请求。
本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。
- 获取 MAC 地址
浏览器得到 IP 地址后,还需要知道目的主机的 MAC 地址。
应用层下发数据给传输层,TCO 协议会指定源端口和目的端口号,然后下发给网络层。
网络层将本地主机作为源地址,获取的 IP 地址作为目的地址,然后下发给链路层。
链路层的发送需要加入通信双方的 MAC 地址,本机 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理:
通过将 IP 地址与本机的子网掩码相与,判断是否与请求主机在同一个子网里:
- 如果在同一个子网里,可以使用 ARP 协议获取到目的主机的 MAC 地址。
- 如果不在同一个子网里,则将请求转发给网关,由它代为转发,同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机的 MAC 地址就应该是 网关的地址。
- TCP 三次握手
此处省略,见上文
- HTTPS 握手
如果使用的是 HTTPS 协议,在通信之前还存在 TLS 的四次握手的过程。
见上文 HTTPS 四次握手
- 返回数据
当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应。
浏览器接收到响应后,开始对 html 文件进行解析,开始页面的渲染过程
- 页面渲染
浏览器首先根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树。
如果遇到 script 标签,判断是否含有 defer 或 async 属性,如果没有,则会阻塞页面渲染,加载并执行 script 标签。
当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树。
渲染树构建好后,根据渲染树进行布局。
布局完成后,使用浏览器的 UI 接口对页面进行绘制,此时整个页面就显示出来了。
- TCP 四次挥手
此处略,见上文
浏览器原理
回流与重绘
回流
当 DOM 的几何尺寸发生变化时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置可能也会因此受到影响),然后将计算结果绘制出来。这个过程就是回流(也叫重排)。
常见引起回流的设计:
- 添加或者删除可见的 DOM 元素
- 元素尺寸改变(边距,填充,边框,宽高)
- 浏览器窗口尺寸改变
- 计算 offsetWidth 和 offsetHeight 属性
重绘
DOM 的样式发生修改,但几何属性没有受到影响时(例如修改颜色、背景色、透明度),就会触发重绘,浏览器直接为元素绘制新的样式。这个过程就是重绘。
局部范围重排
只改变一个固定宽高的 dom 的内部元素,则不会影响外界。
回流必将引起重绘,重绘不一定会引起回流。
渲染树
构建渲染树流程:
- 从 DOM 树的根节点开始遍历每个可见节点。
- 对于每个可见的节点,找到 CSSOM 树中对应的规则,并应用它们。
- 根据每个可见节点以及其对应的样式,组合生成渲染树。
什么是不可见节点
- 一些不会渲染输出的节点,比如 script、meta、link 等。
- 一些通过 css 进行隐藏的节点。比如 display : none。注意,使用 visibility 和 opacity 隐藏的节点,还是会显示在渲染树上的(因为还占据文档空间),只有 display : none 的节点才不会显示在渲染树上。
渲染队列
当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。(类似 tick)
跨域
出于安全考虑,浏览器同源策略的限制
协议、域名、端口必须都一致,否则就是跨域。
解决方式:
- jsonp
原理:
缺点:只能发送 get 请求,不安全,不容易维护,需要前后端协商。
后端返回的数据是一个函数的调用,但是这个函数是在前端定义的,他会把值注入到这个函数的参数里面。
const jsonp = (name) => {
let script = document.createElement('script');
script.src = 'http://localhost:3000/api/jsonp?callback=' + name;
document.body.appendChild(script);
return new Promise((resolve) => {
window[name] = (data) => {
resolve(data);
}
})
}
jsonp(`callback${new Date().getTime}`).then(res => {
console.log(res);
})
假设传入jsonp函数的 name 参数为 callback1626748064645:
- 由于我们在 jsonp 函数中定义了一个全局函数 window[name],也就是
window.callback1626748064645 - 所以当接收到后端的数据(假设为
callback1626748064645({ result: "Hello, World!" });),则会立即作为 js 执行。 - 从而将需要的数据(
{ result: "Hello, World!" })作为参数传入了刚刚定义的全局函数,又由于 Promise 的 resolve,我们可以在jsonp().then() 中获取到这个需要的数据。
- 前端代理
纯前端解决
以 vite 举例:
// in vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true // 跨域
}
}
}
})
这种方式在打包后不会生效,也就是只对开发环境有效。
当网页部署时,一般都是和后端服务器同源的,如果不是,则要靠 nginx 解决。
- 后端设置请求头
响应头添加字段:'Access-Control-Allow-Origin': '*'
不安全,这样子任何人都可以调用这个接口。
可以指定一些允许的 IP 和 端口,而不是 *
- nginx 代理
/etc/nginx/sites-available/default 下添加
server {
location /api {
# 不能写 localhost 要写 ip(自行获取)
proxy_pass http://172.24.176.1:3000; # 将 /api 代理到 http://172.24.176.1:3000/api 下
}
}
鉴权
JWT
由三部分组成,每部分之间用 . 隔开,通过加密算法计算得来。
可以设置 用户的id 方便后续鉴权,还可以设置 token 的时效。
前端将 token 存放到 headers 的 Authorization 字段,发送请求给后端时,后端可以知道是哪个授权用户。
token 前按照规范应当添加 'Bearer ',表示使用了 JWT,当然后端对应也要切割字符串为 token。
网络安全
XSS 跨站脚本攻击
XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。
攻击者可以通过 XSS 攻击实现以下操作:
- 获取页面的数据,如 DOM、cookie、localStorage
- DOS 攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器
- 破坏页面结构
- 流量劫持(将链接指向某网站)
反射型 XSS
-
攻击者构造出特殊的 URL,其中包含恶意代码
-
用户打开带有恶意代码的 URL,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
-
用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
-
恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
常见于通过 URL 传递参数的功能,如网站搜索、跳转等。
由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。
存储型 XSS
最严重、最厉害的
- 攻击者将恶意代码注入到数据库里
- 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
常见于带有用户保存数据的网站功能,如论坛发帖、评论区、用户私信等。
DOM 型 XSS
- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL
- 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:
- DOM 型:取出和执行的代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞
- 而另外两种:属于服务端的安全漏洞
预防 XSS 攻击
- 用纯前端的方式,不适用服务器端拼接后返回(不使用服务端渲染)
- 字符串转义
替换字符 < > 为对应的 html 字符实体
value.replace(/</g, '<').replace(/>/g, '>')
- 使用 CSP
CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击
CSRF 跨站请求伪造攻击
Cross-site request forgery
攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。
本质:利用 cookie 会在同源请求中携带发送给服务器 的特点,冒充用户。
常见的 CSRF 攻击
- GET 类型的 CSRF:比如一个网站的 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起请求
- POST 类型的 CSRF:比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单
- 链接类型的 CSRF:比如在 a 标签的 href 属性里构建要给请求,然后诱导用户去点击
预防 CSRF 攻击
- 进行同源检测
服务器根据 http 请求头中的 orgin 和 referer 信息来判断请求是否为允许访问的站点
缺点是:referer 也可以伪造;搜索引擎的请求会被屏蔽(通常会设置为允许,但这也容易被利用)
- 使用 CSRF Token 进行验证
服务器向用户返回一个随机数 Token,当网站再次发起请求时,在请求参数或者表单中加入这个 token,然后服务器对这个 token 进行验证。(必须保证令牌是由服务器动态生成的,而不是客户端生成)
这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题。
缺点:
- 网站中的所有请求都添加上这个 token,操作比较繁琐
- 一般不会只有一台网站服务器,如果请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这个 token 的话,就没有办法验证了。(这种情况可以通过改变 token 的构建方式来解决)
- 对 Cookie 进行双重验证
服务器在用户访问页面时,向请求域名注入一个 Cookie,内容为随机字符串,然后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较来验证。
使用这种方式是利用了攻击者只能利用 cookie,但是不能访问获取 cookie 的特点。
并且这种方法比 CSRF Token 的方法更加方便,并且不涉及到分布式访问的问题。
缺点:如果网站存在 XSS 漏洞的,那么这种方式会失效。同时这种方式不能做到子域名的隔离。
- cookie 属性设置 Samesite
限制 cookie 不能作为被第三方使用,从而可以避免被攻击者利用。
Samesite 一共有两种模式:
- 严格模式:cookie 在任何情况下都不能作为第三方 Cookie 使用
- 宽松模式:cookie 可以被 类型是 GET 且 会发生页面跳转 的请求使用