--- title: 网络 description: 复习用 toc: true categories: [bagu] --- ## tcp报文 TCP 头格式 ## 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四次挥手 客户端主动关闭连接 —— 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发送算法 解决发送数据量太小,头部占比很大,性价比很低(即**糊涂窗口综合症**)。伪代码如下: ``` c++ 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 tcp可靠而udp不可靠具体体现在:udp没有重传、不保证包的到达顺序、没有流量控制、拥塞控制。**只保证首部+数据的校验**。 对于流量控制这一点,除了窗口之外还要知道发送和接收缓存,特别是接收缓存,当窗口满的时候tcp会通过窗口机制通知对方窗口关闭,保证不会溢出,如果对方无视窗口大小,则接收方会直接丢弃;而udp没有窗口控制机制,直接选择丢弃。 ## http和https http1.1性能: - **长连接**:避免每次都建立tcp连接 - **管道传输**:客户端可以并行发送 - **响应队头阻塞(缺点)**:响应方必须按顺序处理并返回一个请求再处理下一个请求 http不安全在于会出现: - **窃听**:明文传输 - **伪装**:不验证身份 - **篡改**:报文不校验 httpS解决http的不安全: - **数据加密**:防止窃听 - **证书**:防止伪装 - **数据校验**:防止篡改 ## tls握手协议(RSA) 1. ClientHello:客户端通过发送"client hello"消息向服务器发起握手请求,该消息包含了**客户端所支持的 TLS 版本、密码组合以供服务器进行选择、还有一个"client random"随机字符串**。 2. ServerHello:服务器发送"server hello"消息对客户端进行回应,该消息包含了**数字证书、服务器选择的密码组合、"server random"随机字符串**。 3. 验证:客户端对服务器发来的证书进行验证(检查数字签名、证书链、证书有效期、证书撤回状态),确保对方的合法身份,并取出证书中的公钥,**然后客户端发送公钥加密的另一个随机字符串"premaster secret (预主密钥)"** 4. 双方生成对称加密密钥:此时双方都拥有了上述的三个随机数,**双方用这三个随机数生成共享密钥KEY** 5. 双方就绪:双方**用KEY加密「finish信号」并发送给对方** 6. 握手完成 其中第3步中的验证证书,**数字签名是对证书进行做摘要,并用CA的私钥对摘要加密得到的,数字签名目的是保证证书的没被篡改。** 验证证书实际上是验证**证书链**: img 1. 客户端收到服务端证书(比如baidu.com),发现不是**根证书**(根证书是预先加载到操作系统/浏览器,一定受信任的),则查看证书颁发机构是图中的「中间证书」,然后向CA请求该证书 2. 请求到「中间证书」后,发现不是根证书,则查看证书颁发机构是图中的「根证书」,如果该根证书是提前安装到操作系统受信任的,那么**使用该根证书的公钥验证中间证书,如果验证通过,那么中间证书就是可信的(没被篡改)** 3. 由于此时中间证书可信了,那么**使用中间证书的公钥验证服务器证书,如果验证通过,那么服务器证书就是可信的(没被篡改)** 整个握手过程,加密/摘要算法一般是: - 非对称加密:RSA等 - 对称加密:AES、3DES等 - 摘要:SHA-256、SHA-1、MD5等 ## tls记录协议 tls握手协议用于连接建立(主要是生成密钥)。 tls记录协议用于后续通信,主要负责消息(HTTP 数据)的压缩、加密、解密认证。 img - 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: ```http # 请求 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时发生的步骤) ![image-20241122004728841](https://cdn.jsdelivr.net/gh/NOS-AE/assets@main/img/image-20241122004728841.png) 1. **本地DNS系统**解析,DNS系统会最终将域名的解析权交给CNAME指向的**CDN专用DNS服务器** 2. CDN的DNS服务器将**CDN的全局负载均衡设备**IP地址返回用户 3. 用户向CDN的全局负载均衡设备发起内容URL访问请求,CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的**区域负载均衡设备**,告诉用户向这台设备发起请求 4. 域负载均衡设备会为用户选择一台合适的**缓存服务器**提供服务,返回其ID地址给用户 5. 用户向缓存服务器发起请求,如果缓存服务器没有用户要的资源,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地缓存,最终返回资源给用户 ## 访问url发生什么 1. DNS:查询DNS缓存/DNS服务器得到域名对应的IP 2. TCP:与ip:port建立tcp连接,分割http报文不超过MSS 3. IP:根据路由表确定下一跳的ip地址(网关),如果ip地址为空(网关为空)说明已经到达了终点 4. MAC:mac地址才能唯一标识一个设备,因此使用arp协议广播获取拥有该下一跳ip的设备mac地址,然后加上mac头、起始分界符、校验和,发送到交换机 5. 交换机:交换机根据目标mac查询mac地址表,将信号发送到对应的端口,如果找不到的话就广播到所有端口,接收信号的设备会检查目的mac是否自己,如果不是则丢弃,否则响应一个mac地址给交换机,交换机将记录端口与该mac地址的映射