TCP详解

  • 基础知识
  • 计算机网络
  • tcp

一、什么是 TCP

  • TCP 是面向连接的(虚连接、逻辑上的连接)传输层协议
  • 提供点对点连接、面向字节流的全双工通信协议
  • 提供可靠、无差错、不丢不重、按需到达的协议

二、TCP报文段格式

  • 有20字节的必填字段,称为固定首部

固定首部每一行32位(8字节),总共5行,所以总共40字节

  • 剩下的是可选字段
  • 以及填充字段,保证报文长度是4字节的整数倍
  1. 源端口:本地端口,即本端发起通信的端口
  2. 目的端口:远程端口,即对端接收通信的端口
  3. 序号:在一个tcp连接中传送的每一个字节都会被编号,这个序号代表的是本报文段中第一个字节的编号。(发送方发给接收方)
  4. 确认号:期望收到的下一个报文段的序号。(接收方发给发送方)
  5. 数据偏移:代表首部长度。首部长度=偏移值*4字节
  6. 保留
  7. 控制位
  • URG: 紧急位。URG=1时,代表数据在发送方的缓存中应当被优先发送
  • ACK: 确认位。连接建立后所有的报文段都必须设置ACK=1
  • PSH: 推送位。PSH=1时,代表数据在接收方的缓存中应当被优先处理
  • RST: 复位。RST=1时,代表连接出错,双方需要释放并重新建立连接
  • SYN: 同部位。SYN=1时,代表这是一个请求、接收建立连接的报文段
  • FIN: 终止位。FIN=1时,代表发送方要释放连接
  1. 窗口: 发送报文端的一方的接收窗口能接收数据的大小。即能够接收对方发送多大的数据量
  2. 检验和: 检验首部+数据用的
  3. 紧急指针: URG=1时,紧急指针记录紧急数据的字节数
  4. 选项: 一些可选字段。最大报文段长度MSS、窗口扩大、时间戳。。。
  5. 填充: 保证报文长度是4字节的整数倍

二、连接管理

连接的生命周期: 连接建立->数据传输->连接释放

2.1 连接建立

2.1.1 三次握手

  • 客户端: SYN=1, seq=x(随机) 进入syn_sent状态
  • 服务端: SYN=1, ACK=1, seq=y, ack =x+1, 由listen状态进入syn_recd状态
  • 客户端: ACK=1, ack=y+1,seq=x+1 进入established状态, 此时报文段已经可以携带需要传输的数据了
  • 服务端: 进入established状态

2.1.2 SYN洪泛攻击

攻击者只发起第一次握手的报文段,服务端接收到后发送第二次握手的报文段然后处于syn_recd状态。

此时如果攻击者不发送第三次握手的报文段,那么服务端就人为攻击者没有收到第二次握手的报文,那么服务端就会重发第二次握手的报文。

攻击者发起大量这样的攻击的话,就会占用服务端大量的资源,甚至导致服务端挂掉。

解决方法是设置SYN Cookie

2.1.3 为什么是三次握手

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

我们来看看 RFC 793 指出的 TCP 连接使用三次握手的首要原因: The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion. 简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱

TCP 两次握手为什么无法阻止历史连接?open in new window

2.2 连接释放

2.2.1 四次挥手

  • 客户端: FIN=1, seq=u, 进入FIN_WAIT_1状态
  • 服务端: ACK=1, seq=v, ack=u+1, 进入CLOSE_WAIT状态,客户端接收到该报文段后进入FIN_WAIT_2状态
  • 服务端: 数据传输
  • 服务端: FIN=1, ACK=1, seq=w, ack=u+1, 进入LAST_ACK状态, 客户端接收到该报文段后进入TIME_WAIT
  • 客户端: ACK=1, seq=u+1, ack=w+1, 服务端接收到该报文段后进入CLOSED状态
  • 客户端: 进入TIME_WAIT后,会等待2MSL(两个最长报文段寿命)的时间,然后进入CLOSED状态

2.2.2 为什么TIME-WAIT要等待2MSL才关闭?

因为要可靠的实现双方的连接释放。

如果服务端第三次挥手的报文段丢失(或者客户端第四次挥手的报文段丢失),导致服务端一段时间内没有收到客户端第四次挥手,此时服务端会重发第三次挥手。

  • 那么有2MSL等待的情况下,客户端就会等待,并收到服务端重发的报文段
  • 没有的情况下,如果是客户端第四次挥手的报文段丢失,客户端在发送完第四次挥手的报文段后不等待2MSL,直接CLOSED,那么就收不到服务端重发的三次挥手的报文段,服务端就无法正常释放连接。

2.2.3 出现大量TIME-WAIT

短时间内服务间出现大量请求,就会建立非常多的连接。在连接释放时,就会出现大量处于TIME_WAIT状态的连接。如果TIME_WAIT状态的连接过多,就会导致一部分内存的占用(一个TIME_WAIT连接占用4k大小)、占用文件描述符、无法创建新连接。

如何解决?

  • 如果是http请求,改用长链接能够有效缓解
  • 修改linux相关内核参数
    • 打开net.ipv4.tcp_timestamps、net.ipv4.tcp_tw_reuse,调大net.ipv4.tcp_max_tw_buckets
    • 如果出现TIME_WAIT过多的是nginx导致的,那就修改内核参数net.ipv4.ip_local_port_range,增大可用端口范围,可以短暂缓解

2.3 查看Linux下的TCP状态

使用netstat -na

三、可靠传输

  • 校验: 固定首部中的校验和
  • 序号: 固定首部的序号。基于序号可以保证数据的有序传输与接收
  • 确认: 固定首部的确认号。接收方接收到报文段后,会将报文段的序号作为确认号,发送确认报文段给发送方,发送方收到后会将已确认发送的数据移除缓冲区
  • 重传:
    • 冗余确认: 发送给接收端的报文段有丢失,那么接收端会重发确认报文段,收到三个相同的确认报文段,发射端就会立刻重发。
    • 超时: 发送发如果一直没有收到确认报文段,那它也会重发要发送的报文段

四、流量控制

让发送方慢点,让接收方来得及接收。TCP使用的是滑动窗口机制实现的流量控制。

在数据传输的过程中,接收方根据自己的缓存大小,动态地调整发送方发送窗口的大小,rwnd接收端窗口大小。

接收端将rwnd设置到确认报文段中传输给发送方,来影响发送方发送窗口的大小。

发送窗口大小= MIN(rwnd接收端接收窗口大小, cwnd拥塞窗口大小)

五、拥塞控制

带宽不够时(需求的资源>可用资源),就需要拥塞控制。

拥塞控制也用的是滑动窗口机制

与流量控制不同的是,接收窗口大小取决于接收端自己的缓存大小,而拥塞窗口的大小取决于所在的网络环境

拥塞控制有四种算法,

  • 慢开始、拥塞避免:

  • 快重传、快恢复:

5.1 慢开始

最开始拥塞窗口大小是1个最大报文段长度,随着传输轮次的增加,窗口大小指数级增加。

直到窗口大小到达慢开始门限(ssthresh), 由于窗口大小是指数级增加的,为了避免造成网络环境的拥塞,达到ssthresh时就会进入拥塞避免

5.2 拥塞避免

每次传输轮次的增加,拥塞窗口大小增加1个单位。

直到出现了网络拥塞,此时拥塞窗口大小重置为1,ssthresh=当前拥塞窗口大小/2,然后重新开始慢开始

5.3 快重传

收到3个相同的确认报文段,说明有报文段丢失。此时发送方就会立刻重传丢失的报文段,不用等待超时的时候再重传。

5.4 快恢复

跟拥塞避免不同的是,拥塞窗口大小重置当前拥塞窗口大小/2,然后进入拥塞避免算法

Loading...