计算机网络

张贤 2020年03月18日 1,784次浏览

OSI 七层协议

  • 应用层
    主要是 HTTP 协议

  • 表示层
    信息的语法语义表示以及它们之间的关联,如加密解密、转换翻译、压缩解压缩等

  • 会话层
    不同机器上的用户之间建立以及管理会话

  • 传输层
    接收上一层数据,在必要时把数据进行分割,并把这些数据交给网络层,且保证这些数据段能够有效到达接收端。解决了传输质量的问题。将大的数据包进行分割(1500 字节),并给每一个小的数据片一个序号,并根据接收方的接收速度和网络质量控制发送速度。主要是 TCP 和 UDP 协议

  • 网络层
    中间传输需要经过多个节点,如何找到目标节点,如何选择最佳路径。网络层决定如何将网络地址翻译成对应的物理地址,综合考虑发送优先权、网络拥塞程度、服务质量、可选路由的花费来决定传输的最佳路径。这一层的数据称为数据包。路由器工作在这一层。主要是 IP 协议

  • 数据链路层
    定义了如何格式化数据进行传输,如何控制对物理介质的访问。提供错误检测和纠正。将比特数据组成了帧。交换机工作在这一层

  • 物理层
    定义了物理设备的标准,如接口类型、传输介质的标准、将比特流转化为电流强弱来进行传输,数模转换与模数转换。网卡工作在这一层

TCP/IP 四层协议

  • 应用层
    相当于 OSI 中的应用层+表示层+会话层
  • 传输层
    相当于 OSI 中的传输层
  • 网络层
    相当于 OSI 中的网络层
  • 链路层
    相当于 OSI 中的数据链路层+物理层

TCP 的三次握手

TCP 是传输控制协议,是面向连接的、可靠的、基于字节流的传输层通信协议,将应用层的数据分割成报文段(报文段长度由 MTU 决定)发送给目标节点的 TCP 层。数据包都有序号,对方收到则发送 ACK 确认,发送方如果在 RTT 内未收到 ACK 则重传。TCP 使用奇偶校验和来检验数据在传输过程中是否出错

TCP 报文头


传输层的包头没有 IP,只有端口

Sequence Number(序号):当前发送报文的第一个数据字节的序号
Acknowledgement Number(确认号):期望下一次对方发过来的报文数据的第一个字节的序号

Offset:由于头部有可选字段,长度不固定,因此需要 Offset 指出 TCP 本次报文的数据距离起始位置有多远

TCP Flags

  • URG:紧急指针标志。1 表示紧急,0 表示忽略紧急指针
  • ACK:确认序号标志。1 表示确认号有效,0 表示确认号无效
  • PSH:push 标志,指示接收方在接收报文之后,应该尽快交给上层应用程序,而不是在缓冲区中排队
  • RST:重置连接标志,重置由于主机崩溃或者其他原因而出现的错误连接,拒绝非法的报文段和连接请求
  • SYN:同步序号,用于建立连接过程。在连接过程中,SYN=1 和 ACK=0 表示没有使用捎带的确认域,SYN=1 和 ACK=1 表示使用了捎带的确认域
  • FIN:finish 标志,用于释放连接

Window(窗口):滑动窗口的大小,用来告知对方自己的缓存大小,以此控制发送端发送数据的速率,达到流量控制的目的

CheckSum(校验和):对 TCP 头部和 TCP 报文计算所得

Urgent Pointer(紧急指针):指出本报文中紧急数据的字节数
可选项:长度可变

三次握手
前两次握手不能携带任何收据,也要消耗掉一个序号。第三次握手可以携带数据

为什么需要三次握手才能建立起连接:

  • 为了初始化 Sequence Number 的值

首次握手的隐患:SYN 超时
Server 收到 Client 的 SYN,回复 SYN-ACK,但是 client 掉线了,最后 Server 未收到 ACK 确认。此时连接既没有失败,但是也还没成功,Server 会不断重试直到超时,Linux 默认等待 63 秒才超时断开连接,重试的间隔从 1 秒开始,每次翻倍。这种机制可能会使得 Server 遭到 SYN Flood 攻击,恶意客户端向服务器建立大量握手,发了第一个握手之后就下线了,耗尽 Server 端的 SYN 队列,从而导致不能处理正常的请求。

在 Linux 下,SYN 队列满了之后,通过 tcp_syncookies 参数回发 SYN Cookie,如果是正常连接,Client 会回发 SYN Cookie,直接建立连接。

建立连接后,Client 出现故障怎么办?
TCP 有保活机制,在 keepAliveTime 内向对方发生保活探测报文,如果未收到响应则继续发送,尝试次数达到保活探测数仍未收到响应则中断连接。

TCP 的四次挥手

连接双方均可以主动发起关闭连接,这里假设 Client 主动发起关闭

  • 第一次挥手:Client 发送一个 FIN,seq=u,用来关闭 Client 到 Server 的数据传送,Client 进入 FINWAIT_1 状态
  • 第二次挥手:Server 收到 FIN 后,发送一个 ACK 给 Client, ack=u+1(一个 FIN 占用一个序号),Server 进入 CLOSE_WAIT 状态
  • 第三次挥手:Server 发送一个 FIN,用来关闭 Server 到 Client 的连接,Server进入 LAST_ACK 状态
  • 第四次挥手:Client 收到 FIN 后,进入 TIME_WAIT 状态。发送一个 ACK 给 Server,Server 进入 CLOSE 状态。Client 等待 2MSL 时间后才进入 CLOSE 状态

服务器在 CLOSE_WAIT 状态,继续向客户端发送最后的数据

客户端在 TIME_WAIT 状态下,必须等待 2MSL 的时间才进入 CLOSED 状态,Linux 的默认 MSL 是 30 秒。

TIME_WAIT 的作用

  • 是为了确保有足够的时间让对方收到 ACK 包,如果对方没有收到 ACK,对方会重新发送 FIN 包。
  • 避免新旧连接混淆。有些路由器会缓存 IP 数据包,如果连接被重用了,那么这些延迟收到的包就有可能会新连接混在一起。但是经过 2MSL 后,本次连接产生的所有报文都从网络中消失了。

为什么需要四次挥手才能断开连接

因为 TCP 是全双工的,发送方和接收方都需要 FIN 报文和 ACK 报文。发送方和接收方都各自需要两次挥手

服务器出现大量 CLOSE_WAIT 状态的原因

客户端关闭 socket 连接,我方忙于读或者写,没有及时关闭连接,于是对应的资源就一直被占用着。通常是:

  • 由于代码里某些连接没有及时释放,需要检查代码,特别是释放资源的代码
  • 线程池的某些配置不合理,特别是处理请求的线程配置,需要结合业务场景排查

可以通过以下命令查看 处于各个状态的连接数量

netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'

Linux 会为每个用户分配有限的文件句柄数,而一个连接就对应一个文件句柄。如果太多连接处于CLOSE_WAIT,可能会耗尽文件句柄数,新的请求进来就会报错:too many open files。

UDP

UDP 报头包括源端口、目的端口、数据包长度、校验和。不支持错误重传,滑动窗口

  • 面向非连接
  • 不维护连接状态,支持同时向多个客户端传输相同的消息
  • 数据报头只有 8 个字节,额外开销较小
  • 吞吐量仅受限于应用程序生成数据的速度,计算机的性能和传输带宽的限制
  • 尽最大努力交付,不保证可靠交付,不需要维持复杂的连接状态
  • 面向报文,不对应用程序的报文信息进行拆分或者合并

TCP 和 UDP 的区别

  • 面向连接和无连接
  • 可靠性
  • 有序性
  • 速度
  • 量级:TCP 属于重量级,UDP 属于轻量级。体现在报文头部大小。TCP 报文头部 20 个字节,UDP 8 个字节

TCP 的滑动窗口

RTT 和 RTO

  • RTT(Round Trip Time):发送一个数据包到收到对应的 ACK,所花费时间
  • RTO(Retransmission TimeOut):重传时间间隔,TCP 发送一个数据包就会启动一个重传定时器,如果 RTO 内收到了 ACK,则销毁这个歌数据包的对应的重传定时器。RTO 需要根据 RTT 动态调整计算

TCP 需要知道网络情况和接收方的接收速度,来决定发送速率,才不会引起网络拥塞导致丢包
TCP 使用滑动窗口做流量控制与乱序重排,有两个作用:

  • 保证 TCP 的流控特性
  • 保证 TCP 的可靠性