AkiraZheng's Time.

计算机网络传输层

Word count: 3.1kReading time: 11 min
2024/10/21

一、TCP

1.1 TCP的包结构

TCP常见的字段有:

  • 序列号seq
  • 确认号ack
  • 错误连接RST:重置连接(复位)
  • 窗口大小:接收方的缓冲区大小,进行流量控制
  • 请求建立一个连接SYN:同步序列号
  • 请求关闭一个连接FIN:结束连接

TCP四元组:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

而五元组相比四元组多了协议类型,即TCPUDP,其中只要五元组有一个不同,就是不同的通信,因此TCP和UDP的端口号是不冲突的,允许有TCP端口号==UDP端口号

其中TCP像三次握手四次挥手ACK确认和序列号重传机制滑动窗口流量控制拥塞控制等都是保证其可靠传输的重要机制

1.2 TCP的三次握手

1)客户端和服务端建立socket

TCP通信中,客户端创建与服务端的socket连接需要经过以下步骤:

  • 创建客户端socket

  • connect连接服务端socket 而创建用于监听的服务端socket需要经过以下步骤:

  • 创建服务端socket文件描述符

  • bind绑定服务端socket

  • listen监听服务端socket

  • accept等待接受客户端socket

那么理论上最多能支持多少个连接?

  • 总理论连接数=IP地址数端口号数=2^32216=248

但是实际上受文件描述符个数、内存大小、进程数等限制,导致实际连接数远远小于这个数,一般只支持65535个连接

2)TCP的三次握手的状态流转

TCP的三次握手过程如下:

  • 第一次握手-客户端:发送SYN+Seq请求连接->进入SYN_SENT状态
    • 随机生成客户端的Seq序列号
  • 第二次握手-服务端:返回SYN+(ACK=Seq_client+1)+Seq确认连接->进入SYN_RCVD状态
    • 随机生成服务端的Seq序列号
    • 将该客户端的socket信息加入半连接队列
  • 第三次握手-客户端:发送(ACK=Seq_server)+Seq确认连接->进入ESTABLISHED状态
    • ESTABLISHED状态下,如果收到RST则进入CLOSED状态
  • 服务端:接收到ACK+Seq确认连接->进入ESTABLISHED状态
    • 将该客户端的socket信息从半连接队列移入全连接队列

SYN洪泛攻击:攻击方短时间内发送大量SYN请求,导致服务端半连接队列满了,无法再接受新的连接,从而导致拒绝服务攻击

解决方法:

  • 增大半连接队列
  • 开启SYN Cookies绕过半连接队列,将SYN请求的Seq序列号加密后发送给客户端,客户端返回ACK时解密后才能建立连接

3)TCP的三次握手的原因

a. 避免历史连接造成资源浪费

如果是两次握手无法阻止历史重复连接的问题:

  • 客户端给服务端发送SYN请求连接,但是因为网络原因导致服务端在客户端重试发送完后还没收到数据(重传的序列号是一样的)
  • 服务端只有两次握手,因此在发送ACK后不知道客户端有没有接收到,就直接进入ESTABLISHED状态建立了连接且允许发送数据,此时只有客户端发送RST时该历史连接才能断开
  • 此时客户端会发起一个新的连接
  • 而在这段时间内由于服务端已经建立连接了,所以已经可以给客户端发消息了,这是一种资源浪费

三次握手的重新连接过程:

两次握手的重新连接过程:

b. 保证通信双方均具备收发能力&&序列号被确认

如果只有两次握手,那么只能保证服务端具备收发能力,而客户端并不一定具备收发能力;同时也不能保证两方的序列号都被确认

4)TCP的三次握手失败分别会发生什么

  • 第一次握手丢包:客户端启动重试机制
  • 第二次握手丢包:客户端和服务端都会重试
  • 第三次握手丢包:服务端会重试

1.3 TCP的四次挥手

1)TCP的四次挥手的状态流转

挥手前客户端服务端都处于ESTABLISHED状态

TCP的四次挥手过程如下:

  • 第一次挥手-客户端:发送FIN请求关闭连接->进入FIN_WAIT_1状态
    • 发送FIN并调用close函数,属于系统调用
  • 第二次挥手-服务端:接收到FIN请求关闭连接->发送ACK确认->进入CLOSE_WAIT状态
    • CLOSE_WAIT状态下会发送未发完的数据
    • 发送ACK属于内核操作
  • 第三次挥手-服务端:发送FIN请求关闭连接,并调用close函数关闭连接->进入LAST_ACK状态
    • 发送FIN并调用close函数,属于系统调用
  • 第四次挥手-客户端:收到服务端的FIN请求关闭连接->发送ACK确认->进入TIME_WAIT状态
    • TIME_WAIT状态下会等待2MSL时间,等待可能丢失的ACK确认
    • 2MSL是为了保证服务端收到客户端ACK确认,如果服务端没有收到ACK确认,那么会重传FIN请求关闭连接
    • TIME_WAIT状态下的socket会等待2MSL时间,然后进入CLOSED状态
    • 客户端发送ACK确认属于内核操作
  • 服务端:收到客户端的ACK确认->进入CLOSED状态

2)TCP的四次挥手的原因

TCP挥手不能跟握手一样只有三次,相比握手中服务端将SYN+ACK合并成一个步骤,而挥手需要分成两个步骤,因为客户端发送FIN后只是代表不会再发送数据了(还可以接收)

但是服务端可能还有数据没发送完,所以需要将FINACK分开,在FINACK中间,服务端处于CLOSE_WAIT状态,这个状态内会发送剩余的数据,然后再发送FIN请求关闭连接

如果只有三次握手,那么服务端发完FIN后也关闭连接无法发送剩余数据了,会导致这部分数据丢失

3)为什么TIME_WAIT状态要等待2MSL

MSL指的是报文最大生存时间,也就是说如果超过这个时间代表报文消失在网络中了

TIME_WAIT设置为2MSL也就是允许报文丢失一次

客户端发送ACK到服务端时最大报文时间是1MSL,而当超时消亡时,服务端也发现收不到ACK了,因此会重新发送FIN,又需要最大经过1MSL时间,所以总共是2MSL

当客户端再次收到服务端第二次发的FINTIME_WAIT时间会重新计时(重置为2MSL)

4)TIME_WAIT过多

TIME_WAIT是主动发起挥手方才会出现的状态

a. 客户端TIME_WAIT过多

客户端TIME_WAIT过多是可以通过端口复用解决的,开启tcp_tw_reuse,客户端会在connect的时候找一个TIME_WAIT超过1s的连接进行复用

所以一般我们都建议通过客户端主动发起挥手,而不是服务端主动发起挥手

b. 服务端TIME_WAIT过多

  • 情况1:使用短连接,导致每次服务端响应完后会主动发起挥手断连
  • 情况2:长连接满了,最大长连接数设置得过小,导致连接满了,服务端主动关闭已经连接的,来腾出文件描述符给新新连接

5)服务端CLOSE_WAIT过多

CLOSE_WAIT是在服务端返回FINclose关闭了连接时才会转成LAST_ACK状态等待客户端的ACK确认

因此可以判断CLOSE_WAIT过多可能是因为没有调用close关闭连接,导致服务端一直处于CLOSE_WAIT状态

1.4 TCP的可靠性

TCP像三次握手四次挥手ACK确认和序列号重传机制滑动窗口流量控制拥塞控制等都是保证其可靠传输的重要机制

1.4.1 TCP的滑动窗口与流量控制

TCP滑动窗口分为发送端窗口接收端窗口,主要作用是防止发送方发送过快导致接收方无法接收,并导致接收方被淹没

滑动窗口内的数据就是缓冲区,可以不用等上一个的ACK,连续发送滑动窗口大小的数据

发送窗口swnd原则上是约等于接收窗口rwnd的,但是由于发送方维护者拥塞窗口cwnd,因此发送窗口swnd的大小实际上是

  • swnd = min(rwnd, cwnd)

1.4.2 TCP的超时重传与拥塞控制

1.4.2.1 重传

1)超时重传:时间驱动

超时重传就是最朴素的,一个包发送出去后,如果在一定时间内没有收到ACK确认,那么就会重传这个包;

由于是以时间为驱动的,因此可能会出现重传较慢的问题

这里有RTT为数据发送到接收ACK所需的往返时间,而RTO为重传超时时间,一般来说有:

  • RTO > RTT

2)快速重传:数据驱动

发送方收到3个重复的ACK就会进行快速重传,但是该重传一个呢?还是重传后面所有的包呢?

  • 重传一个:如果下一个包也丢失的话,就要再次等3个相同ACK后才会重传
  • 重传后面所有的包:如果后面的包没丢失,那么会浪费网络资源

解决办法:用SACK来解决,即选择性重传,只重传丢失的包

1.4.2.2 拥塞控制

拥塞控制一般要经过以下步骤:

  • 慢启动(窗口大小指数上升),直到到达慢启动门限
  • 拥塞避免(窗口大小线性上升)
  • 拥塞发生(更新慢启动门限、更新拥塞窗口
    • 方法1:超时重传
      • 慢启动门限设置为拥塞窗口的一半
      • 拥塞窗口设置为1,会导致发送速度断崖式变慢
      • 慢启动开始指数增长
    • 方法2:快速重传
      • 慢启动门限设置为拥塞窗口的一半
      • 拥塞窗口设置为跟慢启动门限
      • 拥塞避免开始线性增长

1.5 TCP的拆包和粘包

1.5.1 拆包

一个网络包的最大大小为MTU,一般为1500字节

网络包中除开IP头和TCP头外外的数据包称为MSS,一般为1460字节,MSS中也包含了HTTP整个协议的头和content等内容

  • MTU = IP头 + TCP头 + MSS

由于要求数据的最大大小为MSS,因此如果数据包大于MSS,那么就会拆包,将数据包拆分成多个包发送(也就是应用层的一个数据包被拆成多段进行发送)

1.5.2 粘包

TCP有一个Negle算法,会将多个小包合并成一个大包发送,这样可以减少网络包的数量,提高网络的利用率

但是由于TCP基于字节流是没有数据边界的,因此可能会出现粘包问题

解决办法是在应用层加一些边界(如HTTP、HTTPS协议)

  • 定长包:每个包的长度是固定的,不足的用空格填充
  • 特殊字符隔离:在每个包的末尾加上特殊字符,如\r\n

二、TCP和UDP

2.1 TCP和UDP的区别

1)可靠性上

  • TCP:面向连接的协议,提供可靠传输,通过序列号ACK确认机制保证数据的可靠传输
  • UDP:无连接的协议,提供不可靠传输

2)传输方式上

  • TCP:字节流传输,包没有边界(存在拆包粘包问题)
  • UDP:数据报传输,一个一个包发送的,包之间天然有边界

3)传输效率上

  • TCP:由于需要等待连接、等待重传、等待ACK确认等确保可靠性的机制,因此传输效率比UDP低
  • UDP:传输效率高

4)应用场景

  • TCP:适用于要求可靠传输的场景,如文件传输HTTPHTTPS
  • UDP:适用于实时性要求高的场景,如视频直播语音通话DNS
    • 如果视频用UDP,丢失数据只会丢失某一瞬间的画面,而TCP会等待重传,导致卡顿甚至卡死
CATALOG
  1. 一、TCP
    1. 1.1 TCP的包结构
    2. 1.2 TCP的三次握手
    3. 1.3 TCP的四次挥手
    4. 1.4 TCP的可靠性
      1. 1.4.1 TCP的滑动窗口与流量控制
      2. 1.4.2 TCP的超时重传与拥塞控制
        1. 1.4.2.1 重传
        2. 1.4.2.2 拥塞控制
    5. 1.5 TCP的拆包和粘包
      1. 1.5.1 拆包
      2. 1.5.2 粘包
  2. 二、TCP和UDP
    1. 2.1 TCP和UDP的区别