TCP/IP 协议族
TCP/IP 协议族是Internet最基本的协议,HTTP协议是它的一个子集。TCP/IP协议族按层次分为以下四层(网络基础,最好记住):
- 应用层
- 应用层规定了向用户提供应用服务时通信的协议
- TCP/IP 协议族内预存了各类通用的应用服务协议。比如,FTP(File Transfer Protocol,文件传输协议)、DNS(Domain Name System,域名系统)以及HTTP协议
- 传输层
- 传输层对接上层应用层,提供处于网络连接中两台计算机之间的数据传输所使用的协议
- 在传输层有两个性质不同的协议:TCP(Transmission Control Protocol,传输控制协议)和UDP(User Data Protocol,用户数据报协议)
- TCP协议是全双工的,即发送数据和接收数据是同步进行的,就好像我们打电话一样,说话的同时也能听见。TCP协议在建立和断开连接时有三次握手和四次挥手,因此在传输的过程中更稳定可靠但同时就没UDP那么高效了。
- UDP协议是面向无连接的,也就是说在正式传递数据之前不需要先建立连接。UDP 协议不保证有序且不丢失的传递到对端,也就是说不够稳定,但也正因如此,UDP协议比TCP更加高效和轻便。
- 网络层
- 网络层规定了数据通过怎样的传输路线到达对方计算机传送给对方(IP协议等)
- 与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的所用就是在众多的选项内选择一条传输路线。就跟携程提供的回家路线图作用一样。
- 数据链路层
- 用来处理连接网络的硬件部分,包括控制操作系统、硬件的设备驱动、NIC(Network Interface Card,网络适配器,即网卡),及光纤等物理可见部分(还包括连接器等一切传输媒介)。硬件上的范畴均在链路层的作用范围之内。
一般的web应用的通信传输流是这样的:
发送端在层与层之间传输数据时,每经过一层时会被打上一个该层所属的首部信息。反之,接收端在层与层之间传输数据时,每经过一层时会把对应的首部信息去除。
串行连接、持久连接、管道化持久连接、http/2.0多路复用简介
-
串行连接: HTTP有无连接的特性,即每次连接只能处理一个请求,收到响应后立即断开连接。HTTP/1.0 版本(称为串行连接或短连接、短轮询)中每次HTTP通信后都要断开TCP连接,所以每个新的HTTP请求都需要建立一个新的连接。但在现在网站动则几十条HTTP请求的情况下,很容易达到浏览器请求上限,并且每次请求都建立新的tcp连接(每次都有三次握手四次挥别)极大的增加了通信开销。
-
持久连接: 为解决这个问题,有人提出了持久连接(也叫长连接、长轮询)。一定时间内,同一域名下的HTTP请求,只要两端都没有提出断开连接,则持久保持TCP连接状态,其他请求可以复用这个连接通道。HTTP/1.1 实现并默认了所有连接都是持久连接,这样客户端发起多个HTTP请求时就减少了TCP握手造成的网络资源和通信时间的浪费。但是持久连接采用阻塞模式,下次请求必须等到上次响应返回后才能发起,如果上次的请求还没返回响应内容,下次请求就只能等着(就是常说的线头阻塞)。
-
管道化持久连接: 管道化则可以不用等待响应返回而发送下个请求并按顺序返回响应,现代浏览器并未默认开启管道化。(这方面收集到的资料有限不多说了)
-
HTTP/2.0多路复用: 每个HTTP请求都有一个序列标识符,这样浏览器可以并发多个请求,服务器接收到数据后,再根据序列标识符重新排序成不同的请求报文,而不会导致数据错乱( 细节参照此文)。同样,服务端也可以并发返回多个响应给浏览器,浏览器收到后根据序列标识重新排序并归入各自的请求的响应报文。并且同一个域名下的所有请求都复用同一个TCP连接,极大增加了服务器处理并发的上限。
-
WebSocket: WebSocket是HTML5提出的一种客户端和服务端通讯的全双工协议,由客户端发起请求,建立连接之后不仅客户端可以主动向服务端发送请求,服务端可以主动向客户端推送信息。
URI
HTTP协议使用 URI 定位互联网上的资源。概念:
- URI(Universal Resource Identifier:统一资源标识符)
- URL(Universal Resource Locator:统一资源定位符)
- URN(Universal Resource Name:统一资源名称)。
个人理解URI是一个资源文件的不同表示方法的总称。比如一个文件 a.html ,既可以用这个文件的名字 a.html 来表示,也可以用文件路径 www.baidu.com/a.html 来表示,甚至可以用 urn:a:1535-3613 这样的标识符来表示。他们的关系如下:
HTTP版本
HTTP/1.0
最早的http只是使用在一些较为简单的网页上和网络请求上,所以比较简单,每次请求都打开一个新的TCP链接,收到响应之后立即断开连接。
HTTP/1.1
- HTTP/1.1 引入了更多的缓存控制策略,如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等
- HTTP/1.1 允许范围请求,即在请求头中加入
Range头部 - HTTP/1.1 的请求消息和响应消息都必须包含
Host头部,以区分同一个物理主机中的不同虚拟主机的域名 - HTTP/1.1 默认开启持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。
HTTP/2.0
在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream),理解这两个概念是理解下面多路复用的前提。 帧代表数据传输的最小的单位,每个帧都有序列标识表明该帧属于哪个流,流也就是多个帧组成的数据流,每个流表示一个请求。这里有个chrome扩展程序,可以方便的查看当前网站的HTTP请求版本(安装后在chrome开发工具-Network-在Name/Size/Time表格头右键选择Procotol,即可查看协议版本)。
- 新的二进制格式: HTTP/1.x的解析是基于文本的。基于文本协议的解析存在天然缺陷,文本的表现形式有多样性,要做到全面性考虑的场景必然很多。二进制则不同,只识别0和1的组合。基于这种考虑HTTP/2.0的协议解析采用二进制格式,方便且强大。
- 多路复用: HTTP/2.0支持多路复用,这是HTTP/1.1持久连接的升级版。多路复用,就是在一个 TCP 连接中可以存在多条流,也就是可以发送多个请求,服务端则可以通过帧中的标识知道该帧属于哪个流(即请求),通过重新排序还原请求。多路复用允许并发的发起多个请求,每个请求及该请求的响应不需要等待其他的请求或响应,避免了线头阻塞问题。这样某个请求任务耗时严重,不会影响到其它连接的正常执行,极大的提高传输性能。
- 头部压缩: HTTP/1.x的请求和响应头部带有大量信息,而且每次请求都要重复发送,HTTP/2.0使用encoder来减少需要传输的头部大小,通讯双方各自cache一份头部 fields表,既避免了重复头部的传输,又减小了需要传输的大小。
- 服务端推送: 这里的服务端推送指把客户端所需要的css/js/img资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤(从缓存中取)。
HTTP/3.0
HTTP/2.0 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,那就会导致整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。反而对于 HTTP/1.0 来说,可以开启多个 TCP 连接,出现丢包反倒只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。 出现包阻塞的原因是因为底层TCP协议导致的问题,但是修改TCP协议是不现实的问题,就像typeof null === 'object'一样,修改这个问题会导致出现更多的问题。既然不能修改你,那就另起一个协议取代你。Google 基于 UDP 协议推出了一个的 QUIC 协议,并且使用在了 HTTP/3 上。
QUIC 基于 UDP,但是UDP本身存在不稳定性等诸多问题,所以QUIC在UDP的基础上新增了很多功能,比如多路复用、0-RTT、使用 TLS1.3 加密、流量控制、有序交付、重传等等功能。优点诸多,参考这里:
- 避免包阻塞: 多个流的数据包在TCP连接上传输时,若一个流中的数据包传输出现问题,TCP需要等待该包重传后,才能继续传输其它流的数据包。但在基于UDP的QUIC协议中,不同的流之间的数据传输真正实现了相互独立互不干扰,某个流的数据包在出问题需要重传时,并不会对其他流的数据包传输产生影响。
- 快速重启会话: 普通基于tcp的连接,是基于两端的ip和端口和协议来建立的。在网络切换场景,例如手机端切换了无线网,使用4G网络,会改变本身的ip,这就导致tcp连接必须重新创建。而QUIC协议使用特有的UUID来标记每一次连接,在网络环境发生变化的时候,只要UUID不变,就能不需要握手,继续传输数据。
HTTP报文
用于HTTP协议交互的信息被称为HTTP报文。客户端的HTTP报文叫请求报文,服务端的HTTP报文叫响应报文。
请求报文 是由请求行(请求方法、协议版本)、请求首部(请求URI、客户端信息等)和内容实体(用户信息和资源信息等,可为空)构成。
响应报文 是由状态行(协议版本、状态码)、响应首部(服务器名称、资源标识等)和内容实体(服务端返回的资源信息)构成。
请求方法
- GET:get方法一般用于获取服务器资源
- POST:post方法一般用于传输实体主体
- PUT:put方法一般用于传输文件
- DELETE:delete方法用于删除文件
- HEAD:head方法用于获取报文首部,不返回报文主体
- OPTIONS:options方法用于询问请求URI资源支持的方法
概念很简单很精辟,还不太理解应用场景的自行百度~~
状态码
HTTP状态码表示客户端HTTP请求的返回结果、标识服务器处理是否正常、表明请求出现的错误等。
| 2XX | 成功(这系列表明请求被正常处理了) |
|---|---|
| 200 | OK,表示从客户端发来的请求在服务器端被正确处理 |
| 204 | No content,表示请求成功,但响应报文不含实体的主体部分 |
| 206 | Partial Content,进行范围请求成功 |
| 3XX | 重定向(表明浏览器要执行特殊处理) |
|---|---|
| 301 | moved permanently,永久性重定向,表示资源已被分配了新的 URL |
| 302 | found,临时性重定向,表示资源临时被分配了新的 URL |
| 303 | see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源(对于301/302/303响应,几乎所有浏览器都会删除报文主体并自动用GET重新请求) |
| 304 | not modified,表示服务器允许访问资源,但请求未满足条件的情况(与重定向无关) |
| 307 | temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 |
| 4XX | 客户端错误 |
|---|---|
| 400 | bad request,请求报文存在语法错误 |
| 401 | unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 |
| 403 | forbidden,表示对请求资源的访问被服务器拒绝,可在实体主体部分返回原因描述 |
| 404 | not found,表示在服务器上没有找到请求的资源 |
| 5XX | 服务器错误 |
|---|---|
| 500 | internal sever error,表示服务器端在执行请求时发生了错误 |
| 501 | Not Implemented,表示服务器不支持当前请求所需要的某个功能 |
| 503 | service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 |
302 和 304
302重定向是在一个网站或网页在短时间内临时移到其它位置的情况下使用,这时候就是做临时性的跳转了。
但是302跟网址“劫持”有着莫大的关系。大部分搜索引擎在大部分情况下,当收到302重定向时,一般只要去抓取目标网址即可。但是有时候搜索引擎(以Google为例)并不能总是抓取目标网址,比如说a网址很短,但是它做了302重定向到b网址,而b网址是一个很长的乱七八糟的URL网址,这时候Google很有可能仍显示网址a,这时候就造成了网址劫持的可能性。如果一个居心叵测的人将一个网址a通过302重定向到你的网址b,而Google搜索结果仍然是A,这种情况就是网址劫持。同时,还容易导致网站被降权,所以尽量不用。
304响应也是一种缓存机制。Web服务器对静态资源文件通常会采取缓存,因此在Web开发中你可以看到大量的304响应。 服务器给出的相应中通常会包含Etag来标识资源ID。
如果客户端发送了一个带条件的GET请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器返回304状态码。304响应不包含消息体,因此以消息头后的第一个空行结尾。
在一条HTTP GET请求中,大致是如下的一个过程:
HTTP 中 GET 和 POST 的区别
- 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 和 post 就一个区别:语义
注:在请求带有 Expect: 100-continue 时,client确实会等待Server应答,服务器响应100 continue,当然如果超时没有收到应答,也会发送body
HTTP 缓存即浏览器缓存
定义
浏览器缓存保存着用户通过 HTTP 获取的所有资源,再下一次请求时可以避免重复向服务器发出多余的请求。
通俗的说,就是在你访问过一次某个网站之后,这个站点的文字、图片等所有资源都被下载到本地了,下次再访问该网站时判断是否满足缓存条件,如果满足就不用再花费时间去等待资源的获取了。
分类
- 强缓存
- 协商缓存
强缓存
浏览器在加载资源时,会先根据本地缓存资源的 header 中的信息判断是否命中强缓存,如果命中则直接使用缓存中的资源不会再向服务器发送请求。
从图中可以看出,强缓存一般是这样一个流程:
- 查看
header头中的Expire和Cache-control来判断是否满足规则; - 如果满足规则,就返回缓存的数据;
- 如果不满足规则,就向服务器发送请求;
- 服务器返回数据;
- 将新数据存入缓存。
所以我们主要就是关注 Expire 和 Cache-control 这两个字段。
Expire
Expire 这个字段表示缓存到期时间
通过设置 Expire 来设置缓存有一个致命缺点:
可以看出,这个是个绝对时间,也就是说,如果修改了客户端的本地时间,是不是就会导致判断缓存失效了呢。
Cache-control
既然不能设置绝对时间,那我就设置个相对时间呗。
在 HTTP/1.1 中,增加了一个字段 Cache-Control ,它包含一个 max-age 属性,该字段表示资源缓存的最大有效时间,这就是一个相对时间。
Cache-Control:max-age=600
这个表示的就是最大有效时间是 600s ,对的,它的单位是秒。
Cache-Control 除了 max-age 属性之外还有一些属性:
- no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
- no-store:禁止使用缓存,每一次都要重新请求数据。
- public:默认设置。
- private:不能被多用户共享。
现在基本上都会同时设置 Expire 和 Cache-Control ,Cache-Control 的优先级别更高。
协商缓存(对比缓存)
当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据请求头中的部分信息来判断是否命中缓存。如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。
从图中可以看出,协商缓存一般是这样一个流程:
- 把资源标识,比如
If-Modify-Since或Etag发送到服务器,确认资源是否更新; - 如果资源未更新,请求响应返回的http状态为
304并且会显示一个Not Modified的字符串,告诉浏览器使用本地缓存; - 如果资源已经更新,返回新的数据;
- 将新数据存入缓存。
Last-Modified,If-Modified-Since
浏览器第一次请求资源的时候,服务器返回的 header 上会带有一个 Last-Modified 字段,表示资源最后修改的时间。
Last-Modified: Fri, 27 Oct 2017 07:55:30 GMT
同样的,这是一个 GMT 的绝对时间。
当浏览器再次请求该资源时,请求头中会带有一个 If-Modified-Since 字段,这个值是第一次请求返回的 Last-Modified 的值。服务器收到这个请求后,将 If-Modified-Since 和当前的 Last-Modified 进行对比。如果相等,则说明资源未修改,返回 304,浏览器使用本地缓存。
well,这个方法也是有缺点的:
- 最小单位是秒。也就是说如果我短时间内资源发生了改变,
Last-Modified并不会发生变化; - 周期性变化。如果这个资源在一个周期内修改回原来的样子了,我们认为是可以使用缓存的,但是
Last-Modified可不这样认为。
所以,后来又引入一个 Etag
Etag
Etag 一般是由文件内容 hash 生成的,也就是说它可以保证资源的唯一性,资源发生改变就会导致 Etag 发生改变。
同样地,在浏览器第一次请求资源时,服务器会返回一个 Etag 标识。当再次请求该资源时, 会通过 If-no-match 字段将 Etag 发送回服务器,然后服务器进行比较,如果相等,则返回 304 表示未修改。
Last-Modified 和 Etag 是可以同时设置的,服务器会优先校验 Etag,如果 Etag 相等就会继续比对 Last-Modified,最后才会决定是否返回 304。
总结
除了使用ETag/If-None-Match/If-Match通过文件内容来缓存外,还可以使用Last-Modified/If-Modified-Since通过文件修改时间来进行缓存。 这两者都需要客户端再次发送HTTP请求,如果文件未发生改变,服务器返回304。
而另外一种缓存策略Expires/Cache-Control则可以让客户端避免再次发送请求。一般会优先使用Cache-Control,它能够更加精细地控制缓存策略。
当浏览器再次访问一个已经访问过的资源时,它会这样做:
- 看看是否命中强缓存,如果命中,就直接使用缓存了;
- 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存;
- 如果命中协商缓存,服务器会返回
304告诉浏览器使用本地缓存; - 否则,返回最新的资源。