HTTP
1. HTTP 报文结构
HTTP是应用层协议
- 灵活可扩展,除了规定空格分隔单词,换行分隔字段以外,其他都没有限制,不仅仅可以传输文本,还可以传输图片、视频等任意资源
- 可靠传输,基于 TCP/IP 所以继承了这一特性
- 请求-应答,有来有回
- 无状态,每次 HTTP 请求都是独立的,无关的、默认不需要保存上下文信息
缺点:
- 明文传输不安全
- 复用一个 TCP 链接,会发生对头拥塞
- 无状态在长连接场景中,需要保存大量上下文,以避免传输大量重复的信息
一个HTTP请求报文由请求行(request line)、请求头(header)、空行和请求数据4个部分组成
1.1 通用头(请求头和响应头都有的首部)
字段 | 作用 | 值 |
---|---|---|
Cache-Control | 控制缓存 | public:表示响应可以被任何对象缓存(包括客户端/代理服务器) private(默认值):响应只能被单个客户缓存,不能被代理服务器缓存 no-cache:缓存要经过服务器验证,在浏览器使用缓存前,会对比ETag,若没变则返回304,使用缓存 no-store:禁止任何缓存 |
Connection | 是否需要持久连接(HTTP 1.1默认持久连接) | keep-alive / close |
Transfer-Encoding | 报文主体的传输编码格式 | chunked(分块) / identity(未压缩和修改) / gzip(LZ77压缩) / deflate(zlib结构压缩) |
1.2 请求头
字段 | 作用 | 语法 |
---|---|---|
Accept | 告知服务器客户端可以处理的内容类型 | text/html、image/、/* |
If-Modified-Since | 将Last-Modified 的值发送给服务器,询问资源是否已经过期(被修改),过期则返回新资源,否则返回304 | 示例:If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT |
If-Unmodified-Since | 将Last-Modified 的值发送给服务器,询问文件是否被修改,若没有则返回200,否则返回412预处理错误,可用于断点续传。通俗点说If-Unmodified-Since 是文件没有修改时下载,If-Modified-Since 是文件修改时下载 | 示例:If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT |
If-None-Match | 将ETag 的值发送给服务器,询问资源是否已经过期(被修改),过期则返回新资源,否则返回304 | 示例:If-None-Match: “bfc13a6472992d82d” |
If-Match | 将ETag 的值发送给服务器,询问文件是否被修改,若没有则返回200,否则返回412预处理错误,可用于断点续传 | 示例:If-Match: “bfc129c88ca92d82d” |
Range | 告知服务器返回文件的哪一部分, 用于断点续传 | 示例:Range: bytes=200-1000, 2000-6576, 19000- |
Host | 指明了服务器的域名(对于虚拟主机来说),以及(可选的)服务器监听的TCP端口号 | 示例:Host:www.baidu.com |
User-Agent | 告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本 | User-Agent: Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions> |
1.3 响应头
字段 | 作用 | 语法 |
---|---|---|
Location | 需要将页面重新定向至的地址。一般在响应码为3xx的响应中才会有意义 | Location: |
ETag | 资源的特定版本的标识符,如果内容没有改变,Web服务器不需要发送完整的响应 | ETag: “<etag_value>” |
Server | 处理请求的源头服务器所用到的软件相关信息 | Server: |
1.4 实体头(针对请求报文和响应报文的实体部分使用首部)
字段 | 作用 | 语法 |
---|---|---|
Allow | 资源可支持http请求的方法 | Allow: ,示例:Allow: GET, POST, HEAD |
Last-Modified | 资源最后的修改时间,用作一个验证器来判断接收到的或者存储的资源是否彼此一致,精度不如ETag | 示例:Last-Modified: Wed, 21 Oct 2020 07:28:00 GMT |
Expires | 响应过期时间 | Expires: ,示例:Expires: Wed, 21 Oct 2020 07:28:00 GMT |
2. 请求方法
RESTful架构就是:“每⼀个URI代表⼀种资源,客户端通过四个HTTP动词,对服务器端资源进⾏操作,实现资源的表现层状态转移”。
2.1 RESTful 常用 API
- GET: 获取数据
- HEAD:获取资源的元信息
- POST:提交数据
- PUT:修改数据
- PATCH:更新数据
- DELETE:删除数据
- CONNECT:建立连接隧道,用于代理服务器
- OPTIONS:列出可对资源实行的请求方法,常用于跨域
- TRACE:追踪请求-响应的传输路径
2.2 Get 和 Post 的区别
- 大小区别: Get 传递的内容一般存在大小限制, Post 理论上没有。因为浏览器对 URL 存在限制的,超出 URL 长度的,就会对 URL 字段进行截取。
- 缓存区别: Get 请求会出现浏览器把请求内容缓存下来(这个缓存不一定是304), POST 是没有缓存的。解决缓存问题,一般会在 URL 后边加一个随机数。
- 安全区别:GET 请求 URL 暴露在外边不安全,容易被 URL 劫持。 Post 相对 Get 是安全的。
- 编码区别: GET 请求只能进行 url 编码,只接受 ASCll 字符, POST 没有限制。
- 发送区别: GET 方式会将请求报文一次性发出,而 Post 先发送 header ,服务器响应 100 continue ,然后 body 再发送,服务器响应 200(火狐浏览器 POST 只发送一次)。
2.3 状态码含义
- 1xx: 信息性状态码。
- 100 :接收的请求正在处理。
- 101:切换请求协议,从 HTTP 切换到 WebSocket
- 2xx: 成功状态码
- 200 OK :客户端发送给服务器的请求被正常处理并返回
- 204 No Content :资源请求成功,但是没有资源可返回。响应的报文中不返回任何实体内容
- 206 Partial Content :客户端对服务端的部分内容请求,响应报文中的 Content-Range 字段为请求的范围内容
- 3xx: 重定向状态,资源位置发生变动,需要重新请求
- 301 Moved Permanently :永久重定向。请求的资源已经被永久分配了新的 URL,浏览器会对新的资源进行缓存(更新书签URL)
- 302 Found :临时重定向。请求的资源已经被临时分配了新的 URL,此次请求应该请求新的地址,浏览器对保存的缓存(书签)不会更新
- 303 See Other :和 302 相同,但是服务端要求必须使用 Get 方法请求
- 304 Not Modified :服务器资源未改变,可直接使用客户端未过期的缓存
- 4xx: 客户端请求错误
- 400 Request :请求报文中存在语法错误,需要重新修改请求报文中的内容后发送
- 401 Unauthorized :发送的请求需要通过 HTTP 认证
- 403 Forbidden :客户端没有访问权限
- 404 Not Found :未找到响应的资源
- 405 Method Not Allowed :请求方法不被服务端允许
- 5xx: 服务器端发生错误。
- 500:服务器内部错误,服务器遇到错误,无法完成请求
- 501:尚未实施,服务器不具备完成请求的功能
- 502:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
- 503:服务器当前很忙,暂时无法响应
- 504:请求无法响应
3. Cookie
3.1 Cookie 介绍
由于 HTTP 是无状态的协议,不能保存每一次请求的状态,所以需要给客户端增加 Cookie 来保存客户端的状态。
Cookie 的作用主要用于 用户识别 和 状态管理 。
// 请求首部字段
Cookie: key=value; expires=true, 05 Jul 2016 07:23:31 GMT; path=/; domain=.baidu.com
// 响应首部字段
Set-Cookie:key = value;
- key-value : Cookie 值
- expires=date : Cookie 的有效期(默认的是浏览器关闭之前)
3.2 有效期
Cookie 过期之后,浏览器删除且不再携带 Cookie
- Expires :具体的过期时间
- Max-age :过期的时间范围,浏览器接受 Cookie 开始计时
3.3 作用域
浏览器在发送请求之前,检查 Cookie 的 domain 和 path,如果不一致,则不携带 Cookie。
- path :将服务器的文件目录作为 Cookie 的适用对象
- domain = 域名 : 作为 Cookie 适用对象的域名。如:
naiyou.com
,则www.naiyou.com
、www.naiyou2.com
也可以访问。
3.4 安全性
在 Cookie 传输和管理的时候,要确保 Cookie 的安全性,不被窃取。
- Secure :仅在 HTTPS 安全通信时才会发送 Cookie 。
- HttpOnly :使用 Cookie 不能被 Javascript 脚本访问(防止跨站脚本攻击 XSS 对 Cookie 信息的窃取)。
- SameSite :防止跨站伪造 CORF 的攻击
- Strict :浏览器完全禁止第三方请求携带 Cookie。
www.naiyou.com
只能在www.naiyou.com
下请求。 - Lax :只能在 get 方法提交表单情况或者 a 标签发送 get 请求的情况下可以携带 Cookie。
- None :默认,请求会自动携带上 Cookie。
- Strict :浏览器完全禁止第三方请求携带 Cookie。
3.5 局限性
- 大小:只有 4KB,只能存储少量信息
- 性能:如果不设置 Cookie 的 Domain 和 path ,Cookie 就会被发送到各个域名下,随着请求数量的增多,性能也会出现很大的问题。
- 安全: Cookie 明文传输,被攻击劫持之后,服务器的资源会被窃取。且如果不设置 HttpOnly 攻击者会通过 JS 获取到 Cookie 。
4. Session
4.1 什么是 Session
服务器要给每个客户端分配不同的"身份标识",然后客户端每次向服务器发请求的时候,都带上这个”身份标识“,服务器就知道这个请求来自哪里。而客户端一般使用 Cookie 来保存这个身份信息。
4.2 如何使用
服务端session + 客户端 sessionId
-
用户向服务器发送用户名和密码
-
服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色, 登陆时间等
-
服务器向用户返回一个
session_id
, 写入用户的cookie
-
用户随后的每一次请求, 都会通过
cookie
, 将session_id
传回服务器 -
服务端收到
session_id
, 找到前期保存的数据, 由此得知用户的身份
5. Token
5.1 过程
- 用户通过用户名和密码发送请求
- 程序验证
- 程序返回一个签名的token给客户端
- 客户端储存token, 并且每次用每次发送请求
- 服务端验证Token并返回数据
5.2 数据结构
JSON web Token 由 dot(.) 分隔的三个部分组成
- Header(头部)
- Payload(负载)
- Signature(签名)
xxxxxx.yyyyyyyy.zzzzzzz
5.2.1 Header 头部
Header 是一个 JSON 对象
{
"alg": "HS256", // 表示签名的算法,默认是 HMAC SHA256(写成 HS256)
"typ": "JWT" // 表示Token的类型,JWT 令牌统一写为JWT
}
5.2.2 Payload 负载
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据
{
// 7个官方字段
"iss": "a.com", // issuer:签发人
"exp": "1d", // expiration time: 过期时间
"sub": "test", // subject: 主题
"aud": "xxx", // audience: 受众
"nbf": "xxx", // Not Before:生效时间
"iat": "xxx", // Issued At: 签发时间
"jti": "1111", // JWT ID:编号
// 可以定义私有字段
"name": "John Doe",
"admin": true
}
5.2.3 Signature 签名
Signature 是对前两部分的签名,防止数据被篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
JWT = Base64(Header) + "." + Base64(Payload) + "." + $Signature
5.3 使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务端通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP请求的头信息 Authorization 字段里面。
Authorization: Bearer <token>
5.4 如何保证数据安全
- 发送JWT要使用HTTPS;不使用HTTPS发送的时候,JWT里不要写入秘密数据
- JWT的payload中要设置expire时间
5.5 存储
- 存储在
localStorage
中,每次调用接口的时候放在HTTP
请求头的Authorization
字段里 - 存储在
cookie
中,让它自动发送,缺点是不能跨域
6. Cookie,Session,Token 三者区别
6.1 Cookie 和 Session 的区别
-
存储位置不同: cookie数据放在浏览器上,session数据放在服务器上
-
隐私策略不同:cookie不是很安全, 别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session
-
session会在一定时间内保存在服务器上。当访问增多,就会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
-
存储大小不同: 单个cookie保存的数据不能超过4k, 很多浏览器都限制一个站点最多保存20个cookie
一般建议: 将登陆信息等重要信息存放为session, 其他信息如果需要保留,可以放在cookie中
6.2 Token 和 Seesion 的区别
Session是一种HTTP储存机制, 为无状态的HTTP提供持久机制; Token就是令牌, 比如你授权(登录)一个程序时,它就是个依据,判断你是否已经授权该软件;
Session和Token并不矛盾,作为身份认证Token安全性比Session好,因为每一个请求都有签名还能防止监听以及攻击,而Session就必须依赖链路层来保障通讯安全了。如上所说,如果你需要实现有状态的会话,仍然可以增加Session来在服务端保存一些状态。
6. localStorage
6.1 什么是 localStorage
在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小。
localStorage与sessionStorage的唯一一点区别就是localStorage属于永久性存储,而sessionStorage属于当会话结束的时候,sessionStorage中的键值对会被清空。
6.2 优势与局限
优点
-
localStorage拓展了cookie的4K限制
-
localStorage可以将第一次请求的数据直接存储到本地,这个相当于一个5M大小的针对于前端页面的数据库。
缺点
-
浏览器的大小不统一,并且在IE8以上的IE版本才支持localStorage这个属性。
-
目前所有的浏览器中都会把localStorage的值类型限定为string类型,比较常见的JSON对象类型需要一些转换
-
localStorage在浏览器的隐私模式下面是不可读取的
-
localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡
7. OAuth
- OAuth 2.0是一个开放标准,允许应用程序访问其它应用的用户授权的数据。
8. HTTP 缓存
浏览器中的缓存主要分为强缓存和协商缓存。
- 浏览器在加载资源时,根据请求头的 expires 和 cache-control 判断是否命中强缓存,是则直接从缓存读取资源,不是则发请求到服务器。
- 如果没有命中强缓存,浏览器发送请求到服务器,服务器通过 If-Modified-Since 和 If-None-Match 这些条件请求字段检查资源是否更新:
- 若资源更新,那么返回资源和200状态码
- 若资源未更新,告诉浏览器直接使用缓存获取资源
8.1 强缓存
首先浏览器检查是否为强缓存 ,如果命中,不再给服务器发生请求。否则发起请求,然后进入协商缓存
如何进行检查,主要通过两个字段来进行。
在 HTTP1.0 中,主要通过响应返回的缓存过期时间 Expire 来判断缓存是否过期。 后来在 HTTP1.1 中,增加了 Cache-control 时间段来控制。
// 命中缓存的响应字段
Request Method:GET
Status Code: 200 (from disk cache)
7. 1.1 Expire :过期时间戳
过期时间。这个是服务端告诉客户端,如果你的时间小于我发给你的时间,你就在缓存中取数据。
expires: Wed, 11 Sep 2019 16:12:18 GMT
服务端告诉浏览器,资源你在 9 月 11 号 16:12:18之前可以使用缓存中的数据。浏览器就会拿本地的时间和服务器返回的这个时间对比,如果小于这个时间,就会在缓存中取。 但其实潜藏了一个坑,那就是服务器的时间和浏览器的时间可能并不一致,那服务器返回的这个过期时间可能就是不准确的。
7.1.2 Cache-control
为了弥补上述的缺陷, HTTP1.1 增加了这个属性,主要设置缓存资源过期的时间间隔 。
Cache-Control: max-age=7200
服务器和客户端说,这个资源缓存只可以存在 7200 秒,在这个时间段之内,你就可以在缓存获取资源。解决了上述 Expire 带来的问题。(PS:如果 Expire 和 Cache-control 两者同时出现,则以 Cache-control 为主)
除此之外, s-maxage 也可以做判断依据:
cache-control: max-age=3600, s-maxage=31536000
- 有时候,我们的请求经过代理服务器多次转发,代理服务器也可以缓存数据,这个字段主要用在代理缓存服务器上的。
- Public :只要为资源设置了 public ,那么它既可以被浏览器缓存,也可以被代理服务器缓存
- Private(默认值) :则该资源只能被浏览器缓存
- no-cache :跳过当前的强缓存,发送HTTP请求,即直接进入
协商缓存阶段
- no-store :不使用任何缓存,直接向服务器发起请求
7.2 协商缓存
(需要向服务器询问缓存是否已经过期)
当强缓存过期时间失效或者被设置属性字段绕开,浏览器在请求头中携带相应的缓存tag
来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。
具体来说,这样的缓存tag分为两种: Last-Modified 和 ETag。这两者各有优劣,并不存在谁对谁有绝对的优势
,跟上面强缓存的两个 tag 不一样。
服务端响应信息如下,304 表示服务器资源未改变,可直接使用客户端未过期的缓存。
// 命中缓存的响应字段
Request Method:GET
Status Code: 304 Not Modified
7.2.1 Last-Modified : 最后修改时间
浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段,用于设置协商缓存。
Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
浏览器接收到后,如果再次请求,会在请求头中携带If-Modified-Since
字段,这个字段的值也就是服务器传来的最后修改时间。
If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
服务器拿到请求头中的If-Modified-Since
的字段后,其实会和这个服务器中该资源的最后修改时间对比
- 缓存过期 :如果请求头中的这个值小于最后修改时间,缓存过期,在
Response Headers
中添加新的Last-Modified
值返回给浏览器 - 命中缓存 :否则,返回的 304 响应,让其在本地浏览器缓存取出数据
Last-Modified 缺陷
但是 Last-Modified 存在一个局限性,有以下两种情况:
- 不该请求,还会请求。 编辑文件,内容不变,但是服务器认为文件改动,重新设置缓存时间,当做新请求返回给浏览器
- 该请求,反而没有请求。 修改文件速度很快,快过 If-Modified-Since 字段时间差的检测,文件虽然改动了,但是并没有重新生成新的资源。
7.2.2 Etag :标识字符串
Etag 是由服务器为每个资源生成的唯一的标识字符串。这个标识字符串是基于文件内容编码的,只要文件内容不同,这个编码就不同。
和以上同样的请求过程,浏览器首次发起请求,然后服务器在响应头返回一个标识字符串。
ETag: W/"2a3b-1602480f459"
浏览器再次发起请求,携带一个值相同的字符串。
If-None-Match: W/"2a3b-1602480f459"
服务端接收到该字符串就会作对比,如果相同,则让其读取本地缓存,否则,将新的资源返回给浏览器端。
注意:开启 Etag 文件编码也是需要耗费性能的。
7.3 缓存位置
命中缓存之后,我们需要在缓存中提取数据,缓存位置如下从高到低:
- Memory Cache(内存缓存)
- Service Worker(离线缓存)
- Disk Cache(磁盘缓存)
- Push Cache(推送缓存)—— HTTP2 新特性
7.3.1 Memory 内存缓存(from memory cache)
Memory 为内存缓存,是浏览器最先尝试命中的缓存,也是响应最快的缓存。但是存活时间最短的,当进程结束后,tab 标签关闭后,缓存就不存在了。 因为内存空间比较小,通常较小的资源放在内存缓存中,比如 base64 图片等资源。
7.3.2 Service Worker 离线缓存(from ServiceWorker)
Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM 。 可以帮我们实现离线缓存、消息推送和网络代理等功能。
7.3.3 Disk Cache 磁盘缓存(from disk cache)
内存的优先性,导致大文件不能缓存到内存中,那么磁盘缓存则不同。虽然存储效率比内存缓存慢,但是存储容量有优势。
7.3.4 Push Cache 推送缓存
它是最后一道缓存命中,属于 HTTP2 的内容。
7.4 总结
对浏览器的缓存机制来做个简要的总结:
首先通过 Cache-Control
验证强缓存是否可用
-
如果强缓存可用,直接使用
-
否则进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的
If-Modified-Since
或者
If-None-Match
字段检查资源是否更新
- 若资源更新,返回资源和200状态码
- 否则,返回304,告诉浏览器直接从缓存获取资源
8. HTTP和TCP的不同
HTTP的责任是去定义数据,在两台计算机相互传递信息时,HTTP规定了每段数据以什么形式表达才是能够被另外一台计算机理解。
而TCP所要规定的是数据应该怎么传输才能稳定且高效的传递与计算机之间。
9. TCP 和 UDP
9.1 TCP
- 面向连接
- 每一条 TCP 连接只能是点对点的,即一对一
- 提供可靠交付服务
- 提供全双工通信
- 面向字节流
9.2 UDP
- 无连接
- 尽最大努力交付
- 面向报文
- 无拥塞控制
- 支持一对一,一对多,多对一,多对多的交互通信
TCP 为什么可靠:因为 TCP 有三次握手来保证双方都有接受和发送数据的能力
9.3 TCP 滑动窗口
TCP 滑动窗口分为两种: 发送窗口和接收窗口。
在 TCP 连接中,对于发送端和接收端而言,TCP 需要把发送的数据放到发送缓存区, 将接收的数据放到接收缓存区。而经常会存在发送端发送过多,而接收端无法消化的情况,所以就需要流量控制,发送方的发送窗口不能超过接收方给出的接收窗口的数值。而这种流量控制的过程就需要在发送端维护一个发送窗口,在接收端维持一个接收窗口。
9.4 TCP 协议怎么保证可靠的,UDP 为什么不可靠
- TCP 是面向连接的、可靠的、传输层通信协议
- UDP 是无连接的传输层通信协议,继承 IP 特性,基于数据报
为什么 TCP 可靠?TCP 的可靠性体现在有状态和控制
-
会精准记录哪些数据发送了,哪些数据被对方接收了,哪些没有被接收,而且保证数据包按序到达,不允许半点差错,这就是有状态
-
当意识到丢包了或者网络环境不佳,TCP 会根据具体情况调整自己的行为,控制自己的发送速度或者重发,这是可控制的
-
UDP 就是无状态的和不可控制的
9.5 TCP 拥塞控制原理
原因是有可能整个网络环境特别差,容易丢包,那么发送端就应该注意了。
主要用三种方法:
- 慢开始
- 拥塞避免
- 快重传
- 快恢复
9.5.1 慢开始 + 拥塞避免
对于拥塞控制来说,TCP 主要维护两个核心状态:
- 拥塞窗口(cwnd)
- 慢启动阈值(ssthresh)
在发送端使用拥塞窗口来控制发送窗口的大小。
首先使用慢开始算法来适应网络,在开始传输的一段时间,发送端和接收端会首先通过三次握手建立连接,确定各自接收窗口大小,然后初始化双方的拥塞窗口,接着每经过一轮 RTT(往返时间),拥塞窗口大小翻倍,直到达到慢启动阈值。
然后开始进行拥塞避免,拥塞避免具体的做法就是之前每一轮 RTT,拥塞窗口翻倍,现在每一轮就加一个。
当网络出现超时,发送方判断为网络拥塞。调整慢启动阈值(ssthresh)为原来拥塞窗口的(cwnd)的一半,同时这是拥塞窗口(cwnd) = 1,进入慢开始阶段
9.5.2 快重传
在 TCP 传输过程中,如果发生了丢包,接收端就会发送之前重复 ACK,比如 第 5 个包丢了,6、7 达到,然后接收端会为 5,6,7 都发送第四个包的 ACK,这个时候发送端受到了 3 个重复的 ACK,意识到丢包了,就会马上进行快重传,而不用等到 RTO (超时重传的时间)
选择性重传:报文首部可选性中加入 SACK 属性,通过 left edge 和 right edge 标志那些包到了,然后重传没到的包。
9.5.3 快速恢复
如果发送端收到了 3 个重复的 ACK,发现了丢包,觉得现在的网络状况已经进入拥塞状态了,那么就会进入快速恢复阶段:
- 会将拥塞阈值降低为拥塞窗口的一半
- 然后拥塞窗口大小变为拥塞阈值
- 接着 拥塞窗口再进行线性增加,以适应网络状况
10. HTTPS 的加密方式
具体请看:HTTPS
11. HTTP 中的 Keep-Alive
Keep-Alive
是 HTTP 的一个头部字段 Connetion
中的一个值,保证 HTTP
请求的一个持久连接,建立一次 TCP
连接即可进行多次请求和相应的交互。只要一方没有明确提出断开连接,则保持 TCP
连接状态,减少了 TCP
连接和断开造成的额外开销
12. HTTP2.0
12.1 多路信道复用
单个连接上同时进行多个业务单元数据的传输
12.2 请求优先级
一些比较重要的内容(如网页框架等)即可优先展示
12.3 头部压缩
HTTP2.0可以压缩头部的大小,并且避免了重复的传输,可以大大降低延迟
12.4 服务端推送
服务器在收到客户端对某个资源的请求时,会判断客户端可能还要请求其他的什么资源,然后一同把这些资源都发送给客户端,即便客户端还没有明确表示它需要这些资源。
12.5 二进制帧
13. TCP三次握手和四次分手
13.1 三次握手:为了确认对方的发送和接收能力
三次握手主要流程:
- 一开始双方处于 CLOSED 状态,然后服务端开始监听某个端口进入 LISTEN 状态
- 然后客户端主动发起连接,发送同步位 SYN=1,选择初始序号 seq=x,然后自己变为 SYN-SENT(同步已发送)状态
- 服务端收到之后,向客户端发送确认,确认报文中 SYN=1,ACK=1,确认号 ack = x + 1,序号 seq = y,进入 SYN-REVD(同步收到)状态
- 之后客户端再次发送 ACK=1,seq = x + 1, ack = y + 1 给服务端,变成 EASTABLISHED 状态,服务端收到 ACK,也进入 ESTABLISHED
SYN 需要对端确认,所以 ACK 的序列化要加一,凡是需要对端确认的,都要消耗 TCP 报文的序号
13.1.1 为什么不是两次
无法确认客户端的接收能力。
如果首先客户端发送了 SYN 报文,但是滞留在网络中,TCP 以为丢包了,然后重传,两次握手建立了连接。
等到客户端关闭连接了。但是之后这个包如果到达了服务端,那么服务端接收到了,然后发送相应的数据表,就建立了链接,但是此时客户端已经关闭连接了,所以带来了链接资源的浪费。
13.1.2 为什么不是四次
四次以上都可以,只不过 三次就够了
13.2 四次挥手
- 一开始都处于 ESTABLISH 状态,然后客户端发送 FIN=1,带上 seq = u,状态变为 FIN-WAIT-1
- 服务端收到之后,发送 ACK 确认,确认号 ack = u + 1,然后进入 CLOSE-WAIT 状态
- 客户端收到之后进入 FIN-WAIT-2 状态
- 过了一会等数据处理完,再次发送 FIN=1、ACK=1,seq = w,ack = u + 1,进入 LAST-ACK 阶段
- 客户端收到 FIN 之后,客户端收到之后进入 TIME_WAIT(等待 2MSL),然后发送 ACK 给服务端 ack = w + 1
- 服务端收到之后进入 CLOSED 状态
客户端这个时候还需要等待两次 MSL 之后,如果没有收到服务端的重发请求,就表明 ACK 成功到达,挥手结束,客户端变为 CLOSED 状态,否则进行 ACK 重发
13.2.1 为什么需要等待 2MSL
因为如果不等待的话,如果服务端还有很多数据包要给客户端发,且此时客户端端口被新应用占据,那么就会接收到无用的数据包,造成数据包混乱,所以说最保险的方法就是等服务器发来的数据包都死翘翘了再启动新应用。
- 1个 MSL 保证四次挥手中主动关闭方最后的 ACK 报文能最终到达对端
- 1个 MSL 保证对端没有收到 ACK 那么进行重传的 FIN 报文能够到达
13.2.2 为什么是四次而不是三次
如果是三次的话,那么服务端的 ACK 和 FIN 合成一个挥手,那么长时间的延迟可能让 TCP 一位 FIN 没有达到服务器端,然后让客户的不断的重发 FIN
14. HTTPS
14.1 HTTP 协议的缺点
- 通信使用明文
- 不验证通信方的身份
- 无法验证报文的完整性
14.2 解决方案
-
通信使用明文
使用密文,即:对通信数据进行加密。即便数据被窃听,对方依然需要花费一定的投入来破解,这种高昂的成本间接提高了安全级别。
-
不验证通信方的身份
和服务端使用相同的算法,根据网络请求参数生成一个token,请求/应答时根据token来确定双方的身份。
-
无法验证报文的完整性
使用MD5/SHA1等算法进行完整性验证, 对方接收到数据后,根据同样的算法生成散列值,比对发送方生成的散列值,即可验证数据的完整性。
Https = Http + 加密 + 认证 + 完整性验证
加密,认证和完整性验证这些特点,TCP/IP协议族中已经提供了一个用于数据安全传输的协议——SSL(Secure Socket Layer)安全套接层,虽然也是基于TCP实现的,但SSL并不是应用层协议,而是一个位于应用层与传输层之间的协议,其目的就是为上层的应用层协议提供安全的传输通道。这时Https协议就可用以下形式表示:
Https = Http + SSL
HTTPS并不是一个单独的应用层协议,而只是Http使用SSL通道进行数据传输的过程。
14.3 SSL 协议
SSL(Secure Socket Layer)安全套接层,是一种位于应用层与传输层之间,为网络通信提供安全及完整性验证的一种网络协议。相对于TCP或HTTP协议,SSL协议要复杂很多。但是它也是建立在TCP协议之上的。
SSL协议的握手过程
- 客户端先给服务端发送一个消息,消息内容包括:客户端支持的加密方式,支持的压缩方法,SSL的版本号,客户端生成的随机数,文本内容“Hello”等;
- 服务端接收到消息后,也回发一个Hello,并携带从客户端支持的加密方式中选择的加密方式,服务端生成的随机数,服务端的SSL版本号等信息;
- 随后服务器给客户端发送一个Certificate报文,报文中包含服务端的公钥证书;
- 紧接着服务器给客户端发送Server Hello Done, 表示最初的协商握手过程结束;
- 客户端接收到服务端发送的握手结束的消息后,以Client Key Exchange作为回应,此报文中包含通信加密过程中使用的一种被称为Pre-master secret的随机密码串,并使用第三步接收到的公钥证书进行了加密;
- 接着客户端发送Change Cipher Spec报文,该报文告知服务端,此步骤之后的所有数据将使用第五步中生成的master secret进行加密(master secret的生成过程看后面的介绍);
- 随后客户端发送Finish报文,此报文中包含连接至今所有报文的整体校验值,用于完整性验证;
- 服务端接收到客户端发送的Change Cliper Spec报文后,同样以Change Cliper Spec报文作为回应;
- 接着服务端发送Finish报文给客户端,表示服务端已正确解析客户端发送的整体校验值,至此,SSL握手的过程结束。
- 随后开始使用HTTP协议传输使用master secret加密过的数据。
15. 前后端常用通信方式
15.1 ajax
浏览器发起请求,服务器返回数据,服务器不能主动返回数据,要实现实时数据交互只能是ajax轮询(让浏览器隔个几秒就发送一次请求,然后更新客户端显示。这种方式实际上浪费了大量流量并且对服务端造成了很大压力)。
15.2 webSocket
websocket是HTML5出的东西(协议),是一种全双工通信机制,两端可以及时地互发事件,互发数据,相互通信,只需要浏览器和服务器建立一次连接,服务器就可以主动推送数据到浏览器实现实时数据更新。
长轮询和短轮询,WebSocket 是长轮询。
具体比如在一个电商场景,商品的库存可能会变化,所以需要及时反映给用户,所以客户端会不停的发请求,然后服务器端会不停的去查变化,不管变不变,都返回,这个是短轮询。
而长轮询则表现为如果没有变,就不返回,而是等待变或者超时(一般是十几秒)才返回,如果没有返回,客户端也不需要一直发请求,所以减少了双方的压力。
15.3 区别
本质不同
Ajax 即异步 JavaScript 和 XML,是一种创建交互式网页的应用的网页开发技术
websocket 是 HTML5 的一种新协议,实现了浏览器和服务器的实时通信
生命周期不同:
- websocket 是长连接,会话一直保持
- ajax 发送接收之后就会断开
适用范围:
- websocket 用于前后端实时交互数据
- ajax 非实时
发起人:
- AJAX 客户端发起
- WebSocket 服务器端和客户端相互推送
16. DNS 解析
- 查找浏览器缓存。如果用户的浏览器缓存中没有,浏览器会查找操作系统缓存中是否有这个域名对应的DNS解析结果。
- 查找系统缓存。如果系统缓存中也找不到,那么查询请求就会发向路由器,它一般会有自己的DNS缓存。
- 查找路由器缓存。如果未找到
- 查找ISP DNS 缓存。大约80%的域名解析都到这里就已经完成了,所以ISP DNS主要承担了域名的解析工作。
- 递归搜索。最无奈的情况发生了, 在前面都没有办法命中的DNS缓存的情况下
- 本地 DNS服务器即将该请求转发到互联网上的根域(即一个完整域名最后面的那个点,通常省略不写)
- 根域将所要查询域名中的顶级域(假设要查询ke.qq.com,该域名的顶级域就是com)的服务器IP地址返回到本地DNS
- 本地DNS根据返回的IP地址,再向顶级域(就是com域)发送请求
- com域服务器再将域名中的二级域(即ke.qq.com中的qq)的IP地址返回给本地DNS
- 本地DNS再向二级域发送请求进行查询
- 之后不断重复这样的过程,直到本地DNS服务器得到最终的查询结果,并返回到主机
- 这时候主机才能通过域名访问该网站
参考文章: