网络
复习用
tcp报文
tcp三次握手
- 客户端发送:SYN、随机序列号x
- 服务端发送:SYN、ACK、随机序列号y、确认应答号x+1
- 客户端发送:ACK,可以携带数据
tcp为什么不是两次握手
防止旧的SYN建立连接:如果只有两次握手,那么服务端收到SYN后直接进入established状态(此时可以发送数据),然后返回ack给客户端,如果这个SYN是旧的,那么最终客户端发现不是想要的ack,就会发送rst断开连接,那么服务端又要去断开已经建立好的连接,浪费资源。
如果是三次握手,那么服务端不会直接进入established。
同步序列号:初始化序列号是最重要的,所以客户端发送初始序列号x(第一次握手),客户端需要得知服务端已经收到并且服务端发送初始序列号y(第二次握手),服务端需要得知客户端已经收到(第三次握手)
tcp keepalive
keepalive是TCP保鲜定时器,链接空闲时的心跳机制。
当超过一段时间之后,TCP自动发送一个数据为空的报文给对方,如果对方回应了这个报文,说明对方还在线,链接可以继续保持,如果对方没有报文返回,并且重试了多次之后则认为链接丢失,没有必要保持链接。
tcp四次挥手
- 客户端发送:FIN
- 服务端发送:ACK
- 服务端发送:FIN
- 客户端发送:ACK
- 客户端进入TIME_WAIT,等待2MSL(报文最大生存时间)
其中,客户端一直收不到第三次握手FIN的话,客户端有两种情况:
- 对于客户端调用
shutdown()
的情况,只关闭发送数据不关闭接收数据,因此客户端死等 - 对于客户端调用
close()
的情况,同时关闭发送和接收数据,长时间收不到FIN就会主动close
服务端一直收不到第四次握手ACK的话(在这之前处于CLOSED_WAIT,并且服务端调用close()
,发送了FIN),就会主动close。
tcp四次挥手客户端为什么要TIME_WAIT
原因:
- 等待历史连接的数据都已经在网络中自然消亡:如果没有TIME_WAIT,假设此时客户端建立新的连接,并收到了上个连接中延迟到达的报文,并且序列号恰好在客户端的滑动窗口内,那么则接收到了错误的数据。
- 保证服务端能正确关闭:等待足够的时间让ACK发到对面,如果由于网络原因服务端收不到的话就会重发FIN,客户端收到后重置计时器为2MSL,重传ACK。如果没用TIME_WAIT,客户端收到重传FIN的时候就会回一个RST,虽然服务端也能关闭,但是是将其解释为错误,可能会使用户迷惑。
滑动窗口
滑动窗口用于提高发送数据的速率以及流量控制,每个窗口的单位为1个MSS大小的数据(一个 TCP 报文的最大长度,为了避免超过MTU造成分片,因为丢失一个分片就得重传整个tcp报文)
滑动窗口就是一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。这样一来,就不用发一个数据就等一个ack,可以把窗口的数据连续发送了。
累计确认:ack=n表示序号为n之前的报文都收到了,就算之前的ack都丢失也没关系。
窗口大小:窗口的大小由接收方的窗口大小来决定,由接收方告诉自己还有多少缓冲区可以接收数据,即发送端窗口不能大于接收端窗口
tcp Nagle发送算法
解决发送数据量太小,头部占比很大,性价比很低(即糊涂窗口综合症)。伪代码如下:
1
2
3
4
5
6
7
8
9
10
11
if 有数据要发送 {
if 可用窗口大小 >= MSS and 可发送的数据 >= MSS {
立刻发送MSS大小的数据
} else {
if 有未确认的数据 {
将数据放入缓存等待接收ACK
} else {
立刻发送数据
}
}
}
根据代码,为了避免糊涂窗口综合症,需要:接收方「小窗口直接告诉发送方窗口为0」+ 发送方开启 Nagle 算法
拥塞控制
为了有了流量控制后,还需要拥塞控制?只需要考虑最极端的情况,如果发送方和接收方的传输和接收能力都是无限的,那么瓶颈就出现在网络中,如果无限制地发送,网络只会越来越拥塞,因此需要拥塞控制。
拥塞控制也基于滑动窗口,并加入「拥塞窗口」的概念,因此发送方窗口=min(接收方窗口,拥塞窗口)。
拥塞窗口如何增长:
- 慢启动:每收到一个ACK,拥塞窗口+1。拥塞窗口初始为1,第一次收到ACK,1+1=2,发送两个包,收到两个ACK,2+2=4, 8, 16…慢启动每轮发送是指数增长的。
- 拥塞避免:当慢启动超过阈值,每收到一个ACK,拥塞窗口+1/cwnd,总的来看就是每轮发送才+1,而不是每收到一个ACK+1.拥塞避免每轮发送是线性增长的。
拥塞窗口如何收缩(发生拥塞后):
- 拥塞发生:发生超时重传的时候,慢启动阈值设为cwnd/2,cwnd设为1
- 快速回复:发生快速重传的时候,慢启动阈值设为cwnd/2,cwnd设为慢启动阈值
tcp粘包解决
- 特殊字符作为消息结束符
- 自定义消息结构,比如在头部定义一个消息长度
tcp已经处于established服务端收到新的SYN
服务端会回复属于它的连接的ack,这样客户端发现不是自己想要的ack,就会回一个rst,然后服务端就会释放这个连接
tcp和udp
udp没有重传、不保证包的到达顺序、没有流量控制、拥塞控制。只保证首部+数据的校验
http和https
http1.1性能:
- 长连接:避免每次都建立tcp连接
- 管道传输:客户端可以并行发送
- 响应队头阻塞(缺点):响应方必须按顺序处理并返回一个请求再处理下一个请求
http不安全在于会出现:
- 窃听:明文传输
- 伪装:不验证身份
- 篡改:报文不校验
httpS解决http的不安全:
- 数据加密:防止窃听
- 证书:防止伪装
- 数据校验:防止篡改
tls握手协议(RSA)
- ClientHello:客户端通过发送”client hello”消息向服务器发起握手请求,该消息包含了客户端所支持的 TLS 版本、密码组合以供服务器进行选择、还有一个”client random”随机字符串。
- ServerHello:服务器发送”server hello”消息对客户端进行回应,该消息包含了数字证书、服务器选择的密码组合、”server random”随机字符串。
- 验证:客户端对服务器发来的证书进行验证(检查数字签名、证书链、证书有效期、证书撤回状态),确保对方的合法身份,并取出证书中的公钥,然后客户端发送公钥加密的另一个随机字符串”premaster secret (预主密钥)”
- 双方生成对称加密密钥:此时双方都拥有了上述的三个随机数,双方用这三个随机数生成共享密钥KEY
- 双方就绪:双方用KEY加密「finish信号」并发送给对方
- 握手完成
其中第3步中的验证证书,数字签名是对证书进行做摘要,并用CA的私钥对摘要加密得到的,数字签名目的是保证证书的没被篡改。
验证证书实际上是验证证书链:
- 客户端收到服务端证书(比如baidu.com),发现不是根证书(根证书是预先加载到操作系统/浏览器,一定受信任的),则查看证书颁发机构是图中的「中间证书」,然后向CA请求该证书
- 请求到「中间证书」后,发现不是根证书,则查看证书颁发机构是图中的「根证书」,如果该根证书是提前安装到操作系统受信任的,那么使用该根证书的公钥验证中间证书,如果验证通过,那么中间证书就是可信的(没被篡改)
- 由于此时中间证书可信了,那么使用中间证书的公钥验证服务器证书,如果验证通过,那么服务器证书就是可信的(没被篡改)
整个握手过程,加密/摘要算法一般是:
- 非对称加密:RSA等
- 对称加密:AES、3DES等
- 摘要:SHA-256、SHA-1、MD5等
tls记录协议
tls握手协议用于连接建立(主要是生成密钥)。
tls记录协议用于后续通信,主要负责消息(HTTP 数据)的压缩、加密、解密认证。
- http明文数据分割为消息片段,然后对片段压缩
- 计算压缩片段的MAC值(摘要),保证消息不被篡改。并加上片段的编码,防止重放攻击
- 最后加上数据的元信息
http1.1
相对于http1.0:
- 长连接:解决每次都建立tcp连接
- 管道传输:解决发送方队头阻塞
http2
相对于http1.1:
- 头部压缩:以kv形式缓存头部在服务端,客户端只需要发送对应索引号
- 二进制格式:头部和数据都用二进制格式
- Stream:不同stream可以并发传输,用stream ID保持消息的顺序
- 服务器主动推送:比如渲染页面需要html+css,只需要一次请求,返回html,并且还能主动推送css
http3(QUIC)
相比http2,虽然http2使用了stream解决了http1的队头阻塞,但依然存在tcp层面的队头阻塞,比如:stream2 stream3已经完全到达,但是stream1还未到达,tcp层认为stream2 stream3还不能被应用层接收,因此导致队头阻塞
http3使用了udp,在应用层解决tcp存在的问题:
- 无队头阻塞:依然使用stream,但stream由多个udp包组成,某个stream的udp包丢失不会影响其他stream的接收,解决了队头阻塞
- 更快的连接建立:QUIC包含了tls,因此QUIC握手可以包含tls握手所需的信息,减少了建立连接的通信次数
- 连接迁移:tcp连接基于四元组,而QUIC连接基于连接id,服务端和客户端各自选择一组id标识自己,就算网络IP发生变化(比如wifi变成4g),但由于是同一个设备,只要设备支持quic,保存了id和tls密钥等,继续用他们来通信即可,做到无缝切换。
http和rpc
- 服务发现:http的服务发现比如dns(根据域名发现ip)。rpc的服务发现比如etcd(根据服务找到ip和端口),区别不大
- 底层连接:http的keepalive可以复用tcp连接。rpc也可以基于tcp,并使用连接池复用tcp连接
- 传输内容:rpc可以自定义消息结构,比如protobuf,自定义除冗余字段、压缩字段等,效率更高
综上,rpc更多用于内部服务之间的通信,对外则是使用统一标准的比如http(B/S架构)
http和websocket
由于http设计为一问一答的协议,ws出现之前基于http的「伪」服务端推送:
- 轮询:客户端定时请求,服务端查询数据后马上返回
- 长轮询:客户端请求,服务端挂起这个请求,直到检测到有新数据后再返回,要求服务端有挂起多个请求的能力
ws:
1
2
3
4
5
6
7
8
9
# 请求
Connection: Upgrade
Upgrade: WebSocket
Sec-WebSocket-Key: T2a6wZlAwhgQNqruZ2YUyg==\r\n
# 响应
HTTP/1.1 101 Switching Protocols\r\n
Sec-WebSocket-Accept: iBJKv/ALIW2DobfoA4dmr3JHBCY=\r\n
Upgrade: WebSocket\r\n
Connection: Upgrade\r\n
响应状态码101 switching protocols
ws报文结构,使用「头+数据」解决tcp粘包:
CDN(用户点击URL时发生的步骤)
- 本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器
- CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户
- 用户向CDN的全局负载均衡设备发起内容URL访问请求,CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求
- 域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,返回其ID地址给用户
- 用户向缓存服务器发起请求,如果缓存服务器没有用户要的资源,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地缓存,最终返回资源给用户
访问url发生什么
https://xiaolincoding.com/network/1_base/what_happen_url.html
- DNS:查询DNS缓存/DNS服务器得到域名对应的IP
- TCP:与ip:port建立tcp连接,分割http报文不超过MSS
- IP:根据路由表确定下一跳的ip地址(网关),如果ip地址为空(网关为空)说明已经到达了终点
- MAC:mac地址才能唯一标识一个设备,因此使用arp协议广播获取拥有该下一跳ip的设备mac地址,然后加上mac头、起始分界符、校验和,发送到交换机
- 交换机:交换机根据目标mac查询mac地址表,将信号发送到对应的端口,如果找不到的话就广播到所有端口,接收信号的设备会检查目的mac是否自己,如果不是则丢弃,否则响应一个mac地址给交换机,交换机将记录端口与该mac地址的映射