HTTP的建立
1. OSI七层模型与TCP/IP四层模型
| OSI 七层模型 | OSI 五层模型 | TCP/IP四层模型 |
|---|---|---|
| 应用层 | 应用层 | 应用层 |
| 表示层 | - | - |
| 会话层 | - | - |
| 传输层 | 传输层 | 传输层 |
| 网络层 | 网络层 | 网络层 |
| 链路层 | 链路层 | 网络接口层 |
| 物理层 | 物理层 | - |
2. TCP握手协议(传输层、端对端)
2.1 建立链接
- 第一次握手
客户端主动打开链接,服务端被动打开。客户端发送SYN段(包含了客户端的初始序列号seq=i)
如果半连接队列未满,服务器将该链接的状态变为
SYN_RCVD,并把链接信息放入半连接队列中。否则,服务器不会将链接状态改为
SYN_RCVD,并且丢弃该链接。(SYN flood攻击,DDOS攻击的一种,就是借助这种方式,不断发送
SYN段但是不处理SYN+ACK段,使服务器崩溃。应对方式之一是让syncookies=1,即使半连接队列已满也可以接受非恶意攻击的客户端请求。)(SYN flood攻击的方式其实也分两种,第一种,攻击方的客户端一直发送SYN,对于服务器回应的SYN+ACK什么也不做,不回应ACK, 第二种,攻击方的客户端发送SYN时,将源IP改为一个虚假的IP, 然后服务器将SYN+ACK发送到虚假的IP, 这样当然永远也得不到ACK的回应。)
-
第二次握手
服务端返回
SYN+ACK段,且段中包含服务器的初始序列号seq=j,同时ACK=i+1,表示确认收到客户端的seq。此时,客户端从
SYN_SENT变为ESTABLISHED -
第三次握手
客户端返回
ACK段,ACK=j+1,表示收到了服务器的初始序列号。服务器收到ACK后,如果全连接队列未满,服务器将该链接从
SYN_RCVD变为ESTABLISHED,然后将该链接移出半连接队列,移入全连接队列。若已满,会根据
tcp_abort_on_overflow的值进行执行相应步骤。-
值为0
给客户端定时(二进制指数退让)发送SYN+ACK重新进行第二次握手。
-
值为1
重置连接(发送RST给客户端/忽略客户端传来的包,直到客户端认为异常断开)
-
2.2 断开链接
-
第一次
主动关闭方发送
FIN与seq=u,此时FIN只是意味着客户端不再发送数据,并不意味着他不再接收数据,客户端进入FIN_WAIT1状态。 -
第二次
服务器给客户端发送
FIN与ACK= u+1,seq=v,表示收到了客户端的FIN,此时服务器进入CLOSE_WAIT状态,客户端收到ACK后,进入FIN_WAIT2状态。 -
第三次
服务器确认发送完所有数据后,发送
FIN与seq=w,ACK= u+1给客户端,之后进入LAST_ACK状态。 -
第四次
客户端收到服务器的
FIN后,发送ACK= w+1,seq= u+1。
3. 状态码
-
1xx 信息性,代表请求已被接受,需要继续处理。
-
2xx 成功
- 200 请求已成功
- 201 表示资源已被新建
- 204 成功但是不返回内容
-
3xx 重定向
- 301 资源永久转移
- 302 临时转移
- 304 缓存未过期,可以直接使用
-
4xx 客户端错误
- 400 语法错误,服务器无法理解
- 401 身份认证错误
- 403 无权限访问
- 404 资源找不到
- 409 资源冲突
-
5xx 服务端错误
- 500 未知错误(服务器源代码、数据库错误)
- 502 网关错误
- 503 服务器超载或维护
- 504 网关请求超时
HTTP版本差异
HTTP 1.0
1.0的HTTP是一个无连接、无状态的应用层协议。因此客户端每次请求服务器都需要建立一个TCP链接,服务器处理完TCP立即断开(无连接),服务器也不会记录每次的请求(无状态)。
无状态可以使用
Cookie与Session来实现身份认证与状态记录(状态化)。
问题:
-
无法实现连接复用
每次发送请求后,都需要进行一次TCP连接,TCP的连接与释放过程消耗很多资源,这种无连接特性会导致网络复用率变低。
-
队头阻塞
由于HTTP 1.0规定下一个请求必须在前一个请求响应到达之前才能发送,假设前一个请求响应一直不到达,那么下一个请求将一直阻塞。
HTTP 1.1
长连接
HTTP 1.1解决了连接复用的问题。HTTP 1.1增加了Connection字段,通过设置Keep-Alive使得HTTP连接能够连接不断,避免客户端与服务器多次建立与释放TCP连接,提高了网络的复用率。
当客户端需要关闭连接的时候,可以在请求头的Connection设为false告知服务器关闭。
管道化
基于长连接实现,客户端能够发送多个请求,但是服务器必须按照请求的顺序依次回应相应的结果,即:让队列从客户端的请求队列迁移到了服务器的响应队列,但是仍然没有解决队头阻塞的问题。
缓存处理(强缓存、弱缓存、启发式缓存)
HTTP 2.0
二进制分帧
HTTP 2.0通过在应用层与传输层之间增加了一个二进制分帧层,突破了HTTP 1.0的性能限制,改进了传输性能。
多路复用
- 流:已建立连接上的双向自己流。
- 消息:与逻辑消息对应的完整的一系列数据帧。
- 帧(frame) :HTTP 2.0通信的最小单位,每个帧包含头部,至少也会标识出当前所属的流(stream_id)
每个数据流以消息的形式发送,而消息由一个或者多个帧组成。帧可以乱序发送,然后依赖流标识符重新封装。
并且HTTP 2.0的数据流可以设置优先级与依赖。
头部压缩
在HTTP 1.X中,头部源数据都是以纯文本的形式发送的,会给每个请求头增加较多字节的负荷(Cookie),HTTP 2.0要求客户端与服务器都缓存一张Header_Files表,将Header进行编码压缩,避免了重复Header的传输,减小了传输的负荷。
服务器推送
即客户端发送一次请求,服务器会主动将所需要的资源一次性推送给客户端。eg: 客户端请求一个index.html资源,服务器将在一次请求内将所有所需的index.css, index.js等资源返回给客户。
这里的问题在于当用户误触到某个链接的时候,由于服务器推送机制,用户会一次性获取非常多不必要的资源,这种可能会造成DDoS攻击的实现。
HTTP 3.0
HTTP 2.0成功解决了头部阻塞的问题,但是这里解决的只是应用层的头部阻塞,在传输层的TCP中,出现丢包的情况整个TCP都要等待重传,即传输层的头部阻塞并没有被解决,而修改TCP协议是不可能的(TCP协议由操作系统负责)。
整合
HTTP 2.0默认使用TLS加密,那么除了TCP握手外,还需要建立TLS握手,这样需要消耗非常多的资源(即使TLS 1.3较TLS 1.2已做了优化)。在HTTP 3.0中,TCP握手与TLS握手整合到了一起。即0-RTT连接。
QUIC + UDP
QUIC基于UDP,一个连接上的多个流没有依赖,即使丢包只需要重新发送丢失的包即可,不需要重传整个连接,实现了多路复用。
QUIC的帧中包含了Connection_Id字段,QUIC是通过该段识别连接的,而TCP是基于IP识别连接的,那么当网络环境变化的时候,Connection_Id并不会改变,能够迅速实现重连,能够提供更好的移动端表现。
QUIC除了部分报文外,报文头部与报文BODY都经过加密,有效降低了安全风险。
向前纠错,每个数据包除了它本身的内容之外还包括了其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。
CDN
为什么需要CDN
-
物理因素的限制
美国到中国的网络延迟大约为200ms,而中国到中国的网络延迟大约为20ms,这是由于光速的限制,即使是光纤也无法解决这个问题。
-
宽带成本的限制
假如流量1元1G,发一个10G电影需要十块钱,并且我给10个人发同样的电影需要100元。
-
服务器成本的限制
一台服务器假设能抗100G流量,而双十一的时候,我需要抗1000G流量,这时候我需要10台服务器,而且这10台服务器不是一年到头都在用,只有双十一的时候才会用到,这样就造成了资源的浪费。
CDN的益处
-
降低网络延迟
CDN的作用就是让用户访问最近的服务器,这样就能降低网络延迟。
-
降低部署成本
只需要部署少量的服务器,就能抗住大量的流量。
CDN的原理
一般来说,一个IP只能够对应一台服务器,如何实现用户访问最近的服务器呢?🕵️ - DNS劫持
- 域名解析一般由网站自己处理
- 要加速的域名则重定向到CDN厂商的域名解析服务处理
- CDN厂商的域名解析服务会根据用户的IP地址,解析到最近的服务器
- 用户直接访问最近的CDN服务器
::: info 一些问题
- 地理距离可能更近,但是需要多次路由器节点转发
- 如果用户指定某个DNS,如Google的8.8.8.8 :::
CDN策略
-
拉策略 (轻量资源)
当用户访问一个资源的时候,CDN会先去源站拉取资源,然后再返回给用户。此时,如果CDN没有对应资源,就会返回404;一段时间后,CDN会丢弃资源。
-
推策略 (经常访问的资源)
服务端主动告知CDN存储某些资源,丢弃某些资源。
WebSocket
1. 什么是WebSocket
- 有状态的持久连接
- 服务端主动推送消息
- 用WebSocket发送信息比HTTP延迟低
::: details 一个🌰 ::: code-group
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
ws.send('something');
});
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080');
ws.on('open', function open() {
// 当建立连接的时候,向客户端发送一条信息
ws.send('something');
});
ws.on('message', function message(data) {
// 当收到来自服务端的消息的时候,打印出来
console.log(`received: ${data}`);
});
:::
客户端在HTTP请求头增加:Connection: Upgrade+Upgrade: Websocket
服务端在接受请求后,返回101 Switching Protocols响应
网络安全
三要素
- 机密性:攻击者无法获取通信内容
- 完整性:攻击者对内容进行篡改时能被发现
- 身份验证:攻击者无法伪装成通信双方的任意一方与另一方通信
加密方式
- 对称加密:加密、解密使用相同的密钥
- 非对称加密:加密、解密采用不同的秘钥(公钥和私钥),公钥加密只能私钥解密,私钥加密只能公钥解密
密码散列函数(哈希函数)
- 输入:任意长度的内容
- 输出:固定长度的哈希值
- 性质:找到两个不同的输入使之经过密码散列函数后有相同的哈希值,在计算上是不可能的
机密性
- 加密需要加密算法和秘密钥信息
- 网络传输是明文的,不安全
完整性与身份验证
- 相互关联
如何实现完整性?
- 明文
m,哈希函数H,以及秘钥s - 计算
H(m+s)获取哈希值h - 将
m和h组合成新的信息m+h - 接收方拆分
m+h,重新计算H(m+s)得h',对比h'与h
如何实现身份验证
- 签名:用于鉴别身份与防止伪造
- 数字签名:对明文内容的哈希值使用私钥解密,验证者使用公钥验证