Bootstrap

【Linux】TCP原理

tcp协议段格式

  •  源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
  • 4 位 TCP 报头长度: 表示该 TCP 头部有多少个 32 位 bit(有多少个 4 字节); 所以TCP 头部最大长度是 15 * 4 = 60
  • 16 位校验和: 发送端填充, CRC 校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含 TCP 首部, 也包含 TCP 数据部分.
  • 16 位紧急指针: 标识哪部分数据是紧急数据;
  • 6 位标志位:
  1. URG: 紧急指针是否有效
  2. ACK: 确认号是否有效
  3. PSH: 提示接收端应用程序立刻从 TCP 缓冲区把数据读走
  4. RST: 对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文
  5. SYN请求建立连接; 我们把携带 SYN 标识的称为同步报文段
  6. FIN: 通知对方, 本端要关闭了, 我们称携带 FIN 标识的为结束报文段

4位首部长度深度理解:

 tcp标准报头固定长度为20字节;

4位首部长度表示:tcp标准报头+选项的大小

[0000,1111]转换成十进制[0,15],但是最大是15还没有tcp标准报头的长度大,所以对4位首部长度规定了基本单位:4字节;即[0,60];

如果没有选项的话,4位首部长度假设为x:x*4=20;--->x=5;4为首部长度为0101;

如果右选项的话;4位首部长度范围一定是[20,60],选项大小:(4位首部长度)*4-20即可;


tcp策略

确认应答机制(ACK)

每一个 ACK 都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下
一次你从哪里开始发.

 

上图,既然发送的是一个序号,为什么在协议段中要设置两个序号(序号,确认序号)?

协议段中标记位有什么用?

客户端向服务端发送数据,或者服务端向客户端发送数据,有各种各样的请求,以客户端向服务端发送数据为例:建立连接请求,端口连接请求,确认报文......,不同类型的报文有不同的标记位;


超时重传机制

对方收到数据,就是我收到了ACK;但是,我没有收到ACK,对方没有收到数据(规定);

对于丢包,有两种情况:

(1)主机 A 发送数据给 B 之后, 可能因为网络拥堵等原因, 数据无法到达主机 B;如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发;

 

(2)主机 A 未收到 B 发来的确认应答, 也可能是因为 ACK 丢失了;

因此主机 B 会收到很多重复数据. 那么 TCP 协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉;

那么特点的时间间隔到达是多长呢?

网络状态是会变化的,时间间隔是和网络状态相关联的--------->时间间隔是浮点的!

超时以 500ms 为一个单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍.

累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接


连接管理机制

在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接;

建立连接

SYN置1:请求建立连接;

三次握手:

三次握手,只是一方主动发起,三次握手的过程是双方自动的完成的;

建立连接的备注就是在赌,赌最后一个ACK对方一定收到了!

如果连接不成功?最后一个ACK没有收到怎么办?

连接重置

RST置1:reset连接重置标志位;收到该标志位的主机,要对异常连接重新释放,重新建立;

断开连接

FIN置1:通知对方, 本端要关闭了;

两次握手:

客户端给服务器断开:Client要给服务器发送的数据已经发完,没有数据可发了;

但是服务器给客户端发消息,客户端还是会给服务器ACK;

既然客户端关闭了文件描述符,为什么还可以读取消息呢?

用shutdown可以关闭读端或者写端;

为什么是四次挥手?

用最小的通信成本,建立了断开连接的共识;

双方都不通信了,并且也知道对方也不和我通信了!

四次挥手的过程也是双方自动完成的;

详谈

建立连接,为什么要三次握手?为什么不是一次握手,两次握手?

握手就是发报文;

一次握手:客户端给服务器发送SYN,这样双方默认连接建立好了;

如果一个客户端给服务器发送很多SYN,就会造成SYN洪水,浪费资源;两次握手也是如此;就是造成客户端挂掉;

虽然三次握手也会产生SYN洪水问题,但是相对于一次两次好点;

回到题目,原因:

  1. 验证全双工 ----验证网络的连通性
  2. 建立双方通信共识意愿(最小次数)


CLOSE_WAIT

如果服务器端不关闭文件描述符,就会出现CLOSE_WAIT;

我们编译运行服务器. 启动客户端链接, 查看 TCP 状态, 客户端服务器都为ESTABLELISHED 状态, 没有问题,然后我们关闭客户端程序, 观察 TCP 状态;

此时服务器进入了 CLOSE_WAIT 状态, 结合我们四次挥手的流程图, 可以认为四次挥手没有正确完成;

        对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题;

TMIE_WAIT

主动开始断开连接,自己要处于TIME_WAIT状态,等待需要一定的时长,2*MSL;

打开服务器,用telnet连接服务器,查询:

将服务器关掉,此时服务器是主动断开连接的一方,我们可以看到客户端处于CLOSE_WAIT,

再把客户端关掉,我们就可以看到服务器处于TIME_WAIT状态;

  • TCP 协议规定,主动关闭连接的一方要处于 TIME_ WAIT 状态,等待两个MSL(maximum segment lifetime)的时间后才能回到 CLOSED 状态
  • 我们使用 Ctrl-C 终止了 server, 所以 server 是主动关闭连接的一方, 在TIME_WAIT 期间仍然不能再次监听同样的 server 端口;
  • MSL 在 RFC1122 中规定为两分钟,但是各操作系统的实现不同, 在 Centos7 上默认配置的值是 60s
  • 可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看 msl 的值;
MSL

MSL的TCP报文的最大生存时间;

为什么是2*MSL?

就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);

bind error问题

之前的TCP/UDP  bind  error:
进程已经退了,但是连接还在,即连接对于的端口号还在这里;

怎么解决这个问题呢?

setsockopt可以解决端口号复用问题:

流量控制

看下图:

主机A给主机B发送数据,本质就是主机A的发送缓冲区将数据发送到主机B的接收缓存区,如果主机B的接受缓冲区满了,而主机A并不知道还在发送数据,这就会造成错误;怎么办呢?

只需要在发送报文是在窗口记录自己的剩余空间发送给对方即可;

16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么?

实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口
字段的值左移 M 位;

其实在tcp三次握手的时候,就已经协商交换过了双方的接受能力!!!

如果接受缓冲区的剩余空间大小为0,怎么办?

这时发送方就会等待;

那主机A怎么知道主机B什么时候有空间呢?(两种方法)

1、在等待期间主机A会主动进行窗口更新(tcp报文)查看主机B是否有空间;定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端.

2、主机B有空间后,会向主机A进行窗口更新通知;

那如果主机B的窗口大小一直不更新呢?主机A有没有什么办法尽快的让主机B向上交互?

如果想让对方尽快的处理数据,都可以设置PSH标准位:PSH:PUSH

滑动窗口

目前:滑动窗口的大小=对方同步给我的窗口大小,即对方的接受能力(暂时)

问题列表:

1、滑动窗口只能向右滑动?能不能向左滑动?

只能向右滑动,不能向左

2、滑动是一直不变的吗?可以变得吗?可以变小吗?

滑动窗口可以变大,可以变小

3、可以为0吗?

可以为0

理解滑动窗口

我们知道TCP是面向字节流,每个字节的数据都进行了编号. 即为序列号;

滑动窗口本质就是下标:

在滑动窗口中如果丢包了呢?

丢包分为三种情况:

1、最左侧报文丢失

  • 确认序号规定的约束,滑动窗口左侧不动
  • 快重传&&超时重传,对最左侧报文进行补发

如果2000号报文丢失了,但是3000,4000,5000都收到了;但是发送的ack只能是1001;

  • 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的 ACK, 就像是在提醒发送端 "我想要的是 1001" 一样;
  • 如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
  • 这个时候接收端收到了 1001 之后, 再次返回的 ACK 就是 7001 了(因为 2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
  • 这种机制被称为 "高速重发控制"(也叫 "快重传")

2、中间报文丢失

如果3000号报文丢失了,但是2000,4000,5000都收到了;但是发送的确认序号ack只能是2001;

窗口左侧会更新到2001;就回归到最左侧报文丢失问题;

3、最右侧报文丢失

窗口左侧更新,转换成最左侧丢失问题;

拥塞控制

什么是拥塞控制?

随着网络中的主机增加其发送速率并使网络变得十分拥挤,此时会经常发生丢包现象,导致网络传输效率急剧降低,如果不对这种网络拥塞进行控制,整个网络的吞吐量将随着输入负荷的增大而下降,降低网络传输效率;

TCP拥塞控制算法

4种拥塞控制算法:慢开始,拥塞避免,快重传,快恢复

我们先假设一下条件:

  1. 数据是单方向传送,而另一个方向只传送确认
  2. 接收方总有足够大的缓存空间,因而发生窗口的大小由网络的拥塞程度决定
  3. 以最大报文端MSS的个数为讨论问题的单位,而不是以字节为单位

慢启动 

ssthresh就是阈值,当拥塞窗口小于这个阈值,就是慢启动增长:2^n;超过就是线性增长

拥塞窗口会一直增长直到到达慢开始门限ssthresh,开始执行拥塞避免算法;

拥塞避免

该阶段的拥塞窗口变为线性增长,每次cwnd+1,也即每次增加一个MSS;

随着拥塞窗口的增加,发送速率不断提高,当TCP遇到分组超时重传时,即认为发生了网络拥塞;

此时网络就要做以下工作:

  1. 将阈值更新为发生拥塞时的阈值的一半;
  2. 将拥塞窗口值设置为1,并且重新开始慢启动;

快重传

什么是快重传?

所谓的快重传算法,就是让发送方尽快重传,而不是等待超时重传计时器超时再重传

有时,个别报文端在网络中丢失,但是实际上网络并未发生拥塞,这导致发生方超时重传,误认为网络发生了拥塞;此时发生方将拥塞窗口设置为1,就是降低传输效率;

采用快重传,可以让发生方尽早知道发生了个别报文端的丢失;

  • 要求接收方不要等待自己发送数据时才捎带确认,而是要立即发送确认
  • 即使是失序的报文段,也要立即发送对已收到的报文段的重复确认
  • 发送方一旦收到3个连续的重复确认,就将相应的报文段立即重传,而不是等待该报文的重传计时器超时再重传
快恢复

如果发送方收到了3个重复确认,就执行快恢复算法

慢开始门限sstresh拥塞窗口cwnd都设置为当前拥塞窗口的一半,然后执行拥塞避免算法

详细了解可以看下面视频:

5.5 TCP的拥塞控制_哔哩哔哩_bilibili

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;