头图

一、TCP协议简介

1、数据包的发送流程

image.png
一个数据包,从聊天框里发出,消息会从聊天软件所在的用户空间拷贝到内核空间的发送缓冲区(send buffer),数据包在传输层添加一个TCP头部、在网络层添加一个IP首部,进入到数据链路层添加一个首部和尾部,将其封装为帧,在这里数据包会经过流控(qdisc),再通过RingBuffer发到物理层的网卡。数据就这样顺着网卡发到了纷繁复杂的网络世界里。这里头数据会经过多个路由器和交换机之间的转发和跳转,最后到达目的机器的网卡处。

此时目的机器的网卡会通知DMA将数据包信息放到RingBuffer 接收缓冲区中,再触发一个硬中断给CPU,CPU触发软中断让ksoftirqd去RingBuffer轮询收包,于是一个数据包就这样顺着物理层,数据链路层,网络层,传输层逐层解封,最后从内核空间拷贝到用户空间里的聊天软件里。

数据从发送端到接收端,链路很长,任何一个地方都可能发生丢包,几乎可以说丢包不可避免。
那数据包的可靠传输是如何保证的呢?

2、TCP协议在五层模型中的位置

image.png
计算机网络体系结构中的物理层、数据链路层和网络层,它们共同解决了将主机通过异构网络互联起来所面临的问题,实现了主机到主机的通信。

网络层的IP 协议实现了路由功能,允许某个局域网的 A 主机,向另一个局域网的 B 主机发送消息,而它并不保证数据包的完整。

如何为运行在不同主机上的应用进程提供直接的逻辑通信服务,就是运输层的主要任务。

TCP协议位于传输层,主要解决数据的可靠传输的问题。

3、什么是TCP协议

TCP 是面向连接的、可靠的、基于字节流、全双工的传输层通信协议。

面向连接:要求正式发送数据之前需要通过「握手」建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接。

可靠:无论的网络链路中出现了怎样的链路变化,TCP都能保证数据从A机器的传输层可靠地发到B机器的传输层;

基于字节流:流的含义是没有固定的报文边界。假设你调用 2 次 write 函数往 socket 里依次写 500 字节、800 字节。write 函数只是把字节拷贝到内核缓冲区,最终会以多少条报文发送出去是不确定的。

image.png

全双工:通信的双方在任意时刻既可以是接收数据也可以是发送数据

二、支撑 TCP 协议的基石 —— 首部字段

image.png

1、源端口号、目标端口号

TCP 的报文里是没有源 ip 和目标 ip 的,只有源端口号和目标端口号,因为那是 IP 层协议的事情。

2、序列号

TCP 是面向字节流的协议,通过 TCP 传输的字节流的每个字节都分配了序号,序列号指的是本报文段第一个字节的序列号。用来解决网络包乱序问题。
image.png

3、确认号

TCP 使用确认号来告知对方下一个期望接收的序列号,小于此确认号的所有字节都已经收到。用来解决丢包的问题。
image.png

4、窗口字段

发送本报文段的一方的接收窗口的大小,即接收缓存的可用空间大小,这用来表示接收方的接收能力。
这个字段用来控制发送方的数据发送量,这就是所谓的流量控制。

5、检验和字段

用来检查整个TCP报文段在传输过程中是否出现了误码,如果接收方检测到校验和有差错,则该 TCP 报文段会被直接丢弃。

6、控制位

  • ACK:确认位,该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
  • SYN:同步标志位 ,用于TCP“三次握手”建立连接,该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
  • FIN:终止标志位,该位为 1 时,表示今后不会再有数据发送,希望断开连接。

三、序列号与确认应答机制

TCP为发送的每个字节都分配了序号,并将每个报文段的第一个序号放到首部的序列号上,以便接收的一方按照顺序还原。

确认应答机制就是接收方收到 TCP 报文段后就会返回一个确认应答消息,表示这个序列号以前的数据已经收到。

如果在一定时间内未收到接收方的确认消息,发送方就会意识到消息可能丢失,需要进行重传。

四、重传机制

重传机制是为了保证所有的数据包都可以到达。

1、超时重传

TCP 发送方在发送报文的时候,会设定一个定时器,如果在规定的时间内没有收到接收方发来的 ACK 确认报文,发送方就会重传这个已发送的报文段,如果依旧没有收到回应,就会继续重传。

超时重传时间 RTO 的值应该略大于报文往返 RTT 的值,如果超时重传的数据又超时了该怎么办呢?
TCP 的策略是每次间隔时间都是上次时间的2倍。
image.png

2、快速重传

如果发送 5000 个字节的数据包,因为 MSS 的限制每次传输 1000 个字节,分 5 段传输,如下图:
image.png
数据包 1 发送的数据正常到达接收端,接收端回复 ACK 1001,表示 seq 为1001之前的数据包都已经收到,下次从1001开始发。 数据包 2(10001:2001)因为某些原因未能到达服务端,其他包正常到达,这时接收端也不能 ack 3 4 5 数据包,因为数据包 2 还没收到,接收端只能回复 ack 1001。

第 2 个数据包重传成功以后服务器会回复5001,表示seq 为 5001 之前的数据包都已经收到了。
image.png

超时重传是指当发送端收到 3 个或以上重复 ACK,就意识到之前发的包可能丢了,于是马上进行重传,不用傻傻的等到超时再重传,提高了重传效率。

3、SACK

因为除了 2 号包,3、4、5 包也有可能丢失,那到底是只重传数据包 2 还是重传 2、3、4、5 所有包呢?
聪明的网络协议设计者,想到了一个好办法:

  • 收到 3 号包的时候在 ACK 包中告诉发送端:喂,小老弟,我目前收到的最大连续的包序号是 1000(ACK=1001),[1:1001]、[2001:3001] 区间的包我也收到了
  • 收到 4 号包的时候在 ACK 包中告诉发送端:喂,小老弟,我目前收到的最大连续的包序号是 1000(ACK=1001),[1:1001]、[2001:4001] 区间的包我也收到了
  • 收到 5 号包的时候在 ACK 包中告诉发送端:喂,小老弟,我目前收到的最大连续的包序号是 1000(ACK=1001),[1:1001]、[2001:5001] 区间的包我也收到了
    这样发送端就清楚知道只用重传 2 号数据包就可以了,数据包 3、4、5已经确认无误被对端收到。
    这种方式被称为 SACK。
    image.png

五、流量控制机制

1、基本概念

image.png
TCP 会把要发送的数据放入发送缓冲区(Send Buffer),接收到的数据放入接收缓冲区(Receive Buffer),应用程序会不停的读取接收缓冲区的内容进行处理。

如果发送方发送数据太快而导致接收方来不及接收,就容易造成接收方的接收缓存的溢出,也就是数据的丢失。

TCP为应用程序提供了流量控制机制解决该问题,流量控制的基本方法就是接收端会告知客户端自己接收窗口(rwnd),也就是接收缓冲区中空闲的部分,于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

2、流量控制方法——滑动窗口

从 TCP 角度而言,数据包的状态可以分为如下图的四种:
image.png
发送窗口是 TCP 滑动窗口的核心概念,它表示了在某个时刻一端能拥有的最大未确认的数据包大小(最大在途数据),发送窗口是发送端被允许发送的最大数据包大小,其大小等于上图中 #2 区域和 #3 区域加起来的总大小。

可用窗口是发送端还能发送的最大数据包大小,它等于发送窗口的大小减去在途数据包大小,是发送端还能发送的最大数据包大小,对应于上图中的 #3 号区域。

窗口的左边界表示成功发送并已经被接收方确认的最大字节序号。

窗口的右边界是发送方当前可以发送的最大字节序号,滑动窗口的大小等于右边界减去左边界。
image.png

当上图中的可用区域的6个字节(46~51)发送出去,可用窗口区域减小到 0,这个时候除非收到接收端的 ACK 数据,否则发送端将不能发送数据。滑动窗口变化过程如下:
image.png

3、零窗口探测机制

如果发送端的滑动窗口变为 0 了,而且接收端ACK的报文段在传输过程中丢失了,就会出现互相等待的死锁局面。

于是乎,TCP 又设计了零窗口探测的机制,用来向接收端探测,你的接收窗口变大了吗?我可以发数据了吗?

直到拿到接受窗口变大的ACK就可以继续发送数据了。
image.png

六、拥塞控制机制

1、基本概念

在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就要变坏,这种情况就叫拥塞,若出现拥塞而不进行控制,整个网络的吞吐量就随着负荷的增大而下降。
如果网络上的延时突然增加,而TCP对这个事做出的应对只有重传数据的话,会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,于是,这个情况就会进入恶性循环被不断地放大。
对此TCP的设计理念是:TCP不是一个自私的协议,当拥塞发生的时候,要做自我牺牲。就像交通阻塞一样,每个车都应该把路让出来,而不要再去抢路了。

2、控制方法

名词解释:

  cwnd:拥塞窗口,其值是动态变化的,维护原则是只要网络没有拥塞,就增大,反之就减小
  swnd:发送窗口,在拥塞窗口和接受窗口中取小值,后面的讲解暂不考虑接受窗口,所以swnd=cwnd
  ssthresh:慢开始门限,一般来说ssthresh的值是65535字节,下面的例子使用16
  当cwnd < ssthresh时,使用慢开始算法。
  当cwnd > ssthresh时,停止使用慢开始算法而改用拥塞避免算法。
  当cwnd = ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。

2.1、慢开始

刚刚加入网络的连接,一点一点地提速,慢开始并不慢,这个算法是指数上升的,只是一开始传的数据比较少

慢开始的算法:
1)连接建好的开始先初始化cwnd = 1,表明可以传一个MSS大小的数据。
2)每当收到一个ACK,cwnd++; 呈线性上升
3)每当过了一个RTT,cwnd = cwnd*2; 呈指数让升
4)当cwnd >= ssthresh时,就会进入“拥塞避免算法”

image.png

2.2、拥塞避免

每个传输轮次增加1,这是一个线性上升的算法,使网络没那么容易出现拥塞
image.png

2.3、拥塞算法

1)超时重传,TCP认为这种情况太糟糕,反应也很强烈。

  • sshthresh = cwnd /2
  • cwnd 重置为 1
  • 进入慢启动过程
    image.png

    2)快重传

    所谓快重传就是前面介绍的,不等重传计时器超时再重传,发送方一旦收到3个连续的重复确认就将相应的报文立即重传,这样发送方不会出现超时重传,也就不会误认为出现了拥塞而错误的把拥塞窗口cwnd的值置为1
    image.png

快重传一般与快恢复配合使用,发送方一旦收到2个重复确认,就知道现在只是丢失了个别报文段,于是不启动慢开始算法,而是执行快恢复算法。

2.4、快恢复

算法:

  • cwnd = cwnd /2
  • sshthresh = cwnd
  • 开始拥塞避免算法
    image.png

七、小结

传输层使用 TCP 实现可靠传输,TCP 为了保证可靠传输做了很多工作,主要有以下几种:

  • 使用序列号和确认应答机制检测数据的发送状态;
  • 使用重传机制保证遗漏的数据再次送达;
  • 使用滑动窗口来控制发送的速率,避免接收端缓存的溢出导致的数据丢失;
  • 使用拥塞控制机制来平衡发送数据的大小与拥塞的关系;

TCP的内容非常多,如有理解不到位或者疏漏的地方,希望大家的指正,谢谢!

参考资料
《深入浅出计算机网络》
TCP 的那些事儿(上
用了 TCP 协议,数据一定不会丢吗?
深入理解 TCP 协议:从原理到实战

爆裂Gopher
20 声望11 粉丝

一篇文章讲明白一个知识点,每月更新,欢迎关注与交流。