vlambda博客
学习文章列表

你懂TCP协议不,我不懂!

UDP协议提供了端到端之间的通讯,应用程序只需要在系统中监听一个端口,便可以进行网络通讯。随着计算机网络的发展,计算机网络所承载的业务越来越多,有些业务数据的传输需要具备可靠性,譬如我们在进行在线聊天的时候,我们发送的一条消息,我们必须明确地知道对方是否收到。如果对方对我们发送地消息不给予确认,我们就不知道对方是否已经收到。

显然,UDP协议并没有提供可靠交付的能力,因此我们可能需要在应用层之上,基于UDP构建一套可靠的数据传输协议,有些公司就是这样做的。幸运的是,传输层已经为我们实现了一套可靠的数据传输协议,即TCP协议。

TCP概要

TCP的全称是Transmission Control Protocol,传输控制协议。TCP并不是基于UDP协议构建的,和UDP协议一样是基于IP协议构建的。

TCP主要解决下面的三个问题:

  • 数据的可靠传输。发送方如何知道发出的数据,接收方已经收到。

  • 接收方的流量控制。因为各种原因,接收方可能来不及处理发送方发送的数据,而造成没有及时回应发送方,造成发送方不断的重发数据,最后造成接收方的主机宕机。

  • 计算机网络的拥塞控制。数据在计算机网络之上传输,当出现数据拥塞时如何进行处理(有疑问的你,意思是现实生活中不堵车哦?)

数据的可靠传输(可靠交付)

TCP可靠传输的实现正是基于这样的例子,对于发送方发送的数据,接收方在接受到数据之后必须要给予确认,确认它收到了数据。如果在规定时间内,没有给予确认则意味着接收方没有接受到数据,然后发送方对数据进行重发

重发

当用户交给TCP传输的数据量很大时,如果使用简单的重发机制,即重发所有的数据,势必会严重占用和浪费带宽资源,甚至造成网络拥塞。因此TCP会将用户需要传输的数据进行分组,即将数据进行切割,分成多个数据段(data segment),并给每个数据段编号

TCP报文段首部中通过长度为32bit的字段来表示这个TCP报文段的序号,另外通过长度为32bit的字段来表示确认报文段要确认的报文段的序号。

流量控制

现实生活中,我们去一些热门的景点或者游乐园的某个娱乐项目时,都会需要进行排队,如果是小长假,则会出现人山人海的场景,这是这些机构就会控制每一次参观该景点的人数。
网络应用程序也是如此,当数据到达主机之后,TCP会将该数据放入相应的队列(又称为缓冲区)(如果让你自己基于UDP实现一个TCP模块供自己的应用程序使用,你也会采用这种方式),等待监听该端口的应用程序从队列中获取数据,应用程序一次所能处理的数据有限,因此不可能一次性取出队列中的所有数据,当队列已经满了,则无法再存放新的数据,只能将接受到的数据丢弃,因此TCP协议需要提供流量控制的能力,控制发送方每次发送数据的大小

拥塞控制

现实生活中,高速公路也会堵车,在一段高速公路上,每辆车都在以很快的速度在运行,彼此并没有慢下来,但是为什么还是会出现堵车呢?通常都是因为每段道路的承载能力不一样,譬如当一段8车道公路上的汽车行驶到4车道公路上时,在这两段道路交汇的地方就会出现堵车。

计算机网络是由无数的数据链路组成的,每一段链路的承载能力不一样,也会出现数据拥堵的情况,这通常是由路由器和交换机的处理能力不同造成的。我们还需要知道,这种情况下的拥塞是不能避免的,因为我们无法要求所有链路的承载能力一样,因此我们只能对拥塞进行控制。TCP协议对拥塞控制也提出了响应的解决方案,这也是为什么TCP叫做传输控制协议而不叫做可靠传输协议的原因吧,同时也解释了为什么在计算机网络可靠性能大大提供的今天,TCP还继续发挥着其作用的原因。

TCP连接管理

我们都说TCP是面向连接的,UDP不是面向连接的。那么什么是连接呢?为什么TCP需要面向连接呢?

连接是可靠传输的前提,而不是可靠传输的保证。在QQ这些聊天软件支持离线消息之前,人们在发送消息之前总要确保对方在线,因为如果对方不在线,消息根本就不会被对方收到,也就不存在对你发送的消息进行回应。TCP通过在连接这个动作让接收方知道发送方想要发送数据给它,如果接收方允许,则连接建立,这时双方之间便可以进行数据传输,接收到数据之后须确认数据收到。如果不建立连接,则无法实现可靠传输,因为对于发送方发送的数据,可能接收方的主机根本就没有在监听该端口。例如,在采用UDP的P2P实现中中仍然需要信令服务器,为什么需要信令服务器呢?因为对方可能根本就没有启动该P2P程序或者其它原因无法跟你进行通讯。简而言之,连接的作用就是让通讯双方知道并准备好通讯

连接的建立-三次握手

TCP报文段的类型

TCP属于全双工通信,连接建立之后,任何一方都可以随时发送数据,而不需要理会另外一方是否也在发送数据。加上TCP的确认机制,因此TCP的报文段存在传输有效数据(即用户数据)的报文段,和存在确认数据收到的报文段(ACK,Acknowledgment)。此外还有其他类型的报文段,用来控制连接的建立和拆除TCP连接以及表示紧急数据段。TCP协议通过TCP数据段首部中控制位字段来表示这些报文段的类型,并且采用按位表示,使得一个数据段可以有多种类型。

控制字段取值 控制字段的含义
100000 URG,
010000 ACK,Acknowledgment,确认报文段
001000 PSH
000100 RST,Rest 表示TCP连接中出现差错必须断开连接,然后再重新建立连接
000010 SYN,
000001 FIN,Finally 表示发送此报文段的发送方已经发送完了所有数据,以后不会也不能再发送数据,当另外一方也发送FIN时,则连接断开

实际上,TCP是通过一个或多个控制位来表示TCP报文段的类型。

TCP报文段的序号

TCP的可靠传输是通过确认和重传来实现的,因此

连接的建立过程

TCP连接的建立采用的是C/S(Client/Server,客户端/服务器)模型。由客户端发出建立连接的请求。

下面我们使用'ACK=1'这种方式来表示控制位中的相应位是否置1,'ACK=0'或者不说明则都为0。并使用'ack=1'这种方式表示TCP数据段首部中的确认序号,使用'seq=1'来表示TCP数据段首部中的发送序号。

  • Client首先向Server发出建立连接的TCP报文段(SYN=1,seq=x(表示随机产生一个值,我们假设为100)),并等待Server的确认(确认收到该建立连接的请求)

  • Server收到该建立连接的请求之后,如果同意建立连接,则发送TCP报文段(SYN=1,seq=y(也是随机产生一个值,我们假设为21),ACK=1,ack=x+1(这里表示发送方的x号数据段已经收到,这里为101))给Client表示确认接收到该TCP报文,并等待Client确认该TCP报文已经收到

  • Client收到Server的确认报文段之后,再次向Server发送TCP报文段(seq=x+1(这里为101),ACK=1,ack=y(这里为22)),确认Server的确认报文已经收到。

我们可以看到,控制字段中的SYN是用来建立一个TCP连接的,只在前两次"握手"中置为1,第三次"握手"置为0."我想和你谈话(SYN),我愿意和你谈话(SYN,ACK),那我们开始吧(ACK)".

为什么要使用三次握手

连接的断开

通过TCP三次握手建立的通信属于全双工通信,因此每个方向都必须单独地进行关闭。关闭的原则就是当其中一方A完成数据的传输并且不再传输数据时,发送一个控制位FIN=1的TCP数据段来告知另一方B"我的数据已经传输完毕,并且不再传输数据",另一方B收到数据之后依旧发送确认TCP数据段,来表示A=>B方向的一条连接已经断开,此时TCP连接处于半关闭状态。需要注意的是,这里的不再传输数据是指A不再传输用户数据,对于B传输的数据,A仍然要接收并给予确认

当B发送完数据之后并准备断开连接时,发送一个控制位FIN=1,ACK=1(ack=A的FIN报文段的序号)的TCP报文段,并等待A的确认,A收到B的FIN报文段后给予确认,至此整个TCP连接关闭。

你懂TCP协议不,我不懂!

为了防止因为数据传输延时造成B的FIN报文段比B的有效数据传输报文段(用户数据)提前到达,因A会等待2MSL之后才真正关闭TCP连接。

TCP的有限状态机

状态 描述
CLOSED 呈阻塞,关闭状态,表示当前主机没有活动的传输连接或没有正在进行传输连接
LISTEN 呈监听状态,表示服务器正在等待新的传输连接进入
SYNRCVD 表示服务器已经收到一个传输连接请求,但尚未确认
SYNSENT 表示客户端已经发出一个传输连接请求,等待服务器的确认
ESTABLISHED 传输连接建立
FIN_WAIT_1 主动关闭方的主机已经发送关闭连接请求,等待对方确认
CLOSE_WAIT 被动关闭方的主机收到主动关闭方的关闭连接请求,并已确认
FIN_WAIT_2 主动关闭方的主机已经收到对方对主动关闭连接请求的确认,等待对方发送关闭传输连接请求
LAST_ACT 被动关闭方的主机已经发送关闭连接请求,等到主动方确认
TIME_WAIT 主动关闭方的主机收到对方发送的关闭连接请求
LISTEN 呈监听状态,表示服务器正在等待新的传输连接进入
SYNRCVD 表示服务器已经收到一个传输连接请求,但尚未确认
SYNSENT 表示客户端已经发出一个传输连接请求,等待服务器的确认
ESTABLISHED 传输连接建立
FIN_WAIT_1 主动关闭方的主机已经发送关闭连接请求,等待对方确认
CLOSE_WAIT 被动关闭方的主机收到主动关闭方的关闭连接请求,并已确认
FIN_WAIT_2 主动关闭方的主机已经收到对方对主动关闭连接请求的确认,等待对方发送关闭传输连接请求
LAST_ACT 被动关闭方的主机已经发送关闭连接请求,等到主动方确认
TIME_WAIT 主动关闭方的主机收到对方发送的关闭连接请求

你懂TCP协议不,我不懂!

TCP连接建立:

  • 首先,服务器监听一个TCP端口,此时该端口处于Listen状态。

  • 一段时间后,客户端发送一个TCP连接请求,客户端的端口就出处于SYN_SENT状态,当服务器接收到一个客户端的TCP连接请求时,即收到客户端的SYN报文段,服务器监听的端口就会切换到SYN_RCVD状态,服务器向客户端发送SYN+ACK报文段==>问题0

  • 当客户端收到服务器发送的SYN+ACK报文段时,客户端的端口切换到ESTAB-LISTEN状态,客户端同时向服务器发送SYN报文段确认 ==>问题1

  • 当服务器收到客户端的SYN报文段时,服务器的端口切换到ESTAB-LISTEN状态

问题0:当客户端发送的SYN报文段,没有得到服务器的任何响应怎么办获者收到服务器的REST响应
当客户端发送一个SYN报文段之后就会启动定时器,如果在规定的时间内没有收到服务器的任何响应或者收到服务器的RESR响应,则会重新发送SYN报文段。如果在几次重发后仍然没有得到服务器的SYN+ACK响应就会放弃,端口切换CLOSE状态,并报告上层。

问题1:如果当客户端发送给服务器的SYN报文段丢失了会怎样?

连接仍然能正常工作。因为客户端已经处于ESTABLISHED状态,所以客户端能够像服务器发送数据。由于每个TCP报文段中都有ACK标识位,而在确认序号字段中包含正确的数值。所以当客户端发送的第一个数据到达服务器时,服务器的端口就会切换到ESTABLISHED状态。这实际上是TCP的重点,即每个TCP报文段报告发送方希望看到的下一个序号,即时这个序号与以前的一个或多个报文段包含的序号重复。

TCP连接关闭

TCP的可靠传输

TCP的可靠传输是通过确认和超时重传的机制来实现的,而确认和超时重传的具体的实现是通过以字节为单位的滑动窗口机制来完成。

滑动窗口机制

虽然上层应用和TCP的交互是一次一个数据快(大小不等),但是TCP把上层应用程序交付下来的数据看成仅仅是一串连续的无结构字节流

  • 发送窗口:在为收到对方的ACK确认的情况下,只有发送窗口内的数据才能连续地发送出去。凡事已经发送过的数据,在未收到ACK确认之间都必须暂时保留在发送窗口内,以便超时重传使用。

  • 接收窗口:缓冲区,用来接收发送方的TCP数据段。

你懂TCP协议不,我不懂!

停止-等待协议

发送方和接收方都采用窗口大小为1的滑动窗口,即发送窗口和接受窗口都为1个最大TCP数据段的大小。
停止等待协议的规则是:

  • 发送方发完1个分组并收到接收方ACK确认之后才能发送下一个分组;

  • 如果接收方收到一个错误的分组,则给发送方发送一个否认分组NAK,发送方收到NAK分组后重发,并继续等待发送方的ACK确认

  • 如果发送方在规定的时限内(发送完一个分组,就开启一个定时器)没有收到接收方的ACK确认分组,则重新发送该分组。

你懂TCP协议不,我不懂!

后退N协议

后退N协议思想的是流水线传输,即可以连续发送多个分组,而不必每发完一个分组就等待接收方的ACK确认。

你懂TCP协议不,我不懂!

规则如下:

  • 发送窗口的大小为n,接收窗口的大小为1

  • 发送方在发送完一个数据分组之后,不是停下来等待接收方的ACK确认,而是可以连续再发送若干个分组。

  • 接收方在收到发送方发送的分组之后发送ACK确认分组,并移动接收窗口。

  • 如果发送方发送一共连续发送了5个分组,中间的第3个分组丢失,则接收方要求发送方重传后面的3个分组(第3个、第4个、第5个分组)。(因为第4个和第5个分组不接序)这也说明了该协议为什么叫做后退N协议。

描述:

你懂TCP协议不,我不懂!

虽然在收到了有差错的2号分组之后,收到了正确的3号分组和4号分组,但是由于接收端只按顺序接收数据分组,造成3号和4号分组不能和1号分组接序而被丢弃,等到A的定时器超时重发。

选择重传

选择重传协议是对后退N协议的一种优化,其只是选择性重发那些确实丢失的分组。

规则:

  • 发送窗口的大小为m,接收窗口的大小为n

  • 接收方先接收序号不连续的分组,并发送ACK确认,然后等待发送方重发丢失的分组(发送方每收到一个ACK确认就会关闭相应的定时器,最终没有收到ACK确认的分组的定时器超时,发送方会再次重发)

  • 收到重发的分组后给予ACK确认,再对全部分组进行排序,最后交给上层应用。

你懂TCP协议不,我不懂!

通知窗口

TCP采用通知窗口实现对发送端的流量控制,通知窗口大小的单位是字节。TCP通过在TCP数据段首部的窗口字段中填入当前设定的接收窗口(即通知窗口)的大小,用来告知对方'我方当前的接收窗口大小',以实现流量控制。

通信双方的发送窗口大小由双方在连接建立是商定,在通信过程,双方可以动态地根据自己的情况调整对方的发送窗口大小。

TCP拥塞控制

在某段时间,如果对计算机网络中某些资源的需求超过了所能提供该资源的总和,网络的性能就要变坏——产生拥塞(congestion)。即当需要>供给时会造成网络拥塞。
如果计算机网络中有许多资源同时产生拥塞,网络的性能就要明显变坏,整个网络的吞吐量将随输入负荷的增大而下降。

你懂TCP协议不,我不懂!

TCP协议通过慢启动机制、拥塞避免机制、加速递减机制、快重传和快恢复机制来共同实现拥塞控制。

你懂TCP协议不,我不懂!

在拥塞控制中还有一个"拥塞窗口"的概念,该窗口由发送方根据当前计算机网络的拥塞情况来计算,和通知窗口共通作用于发送窗口,"拥塞窗口"的单位也是字节,通常拥塞窗口的初始值为一个最大TCP报文段的大小是,但下面我们以每个传输轮次所能发送TCP数据段(用户数据)的次数作为其单位来描述。

TCP的慢启动机制、拥塞避免机制和加速递减机制都是通过改变拥塞窗口的大小来时对发送方的发送窗口进行控制。

拥塞控制与流量控制的关系

拥塞控制是一个全局性的控制,涉及到计算机网络中所有的主机、路由器以及降低网络传输性能的相关因素。而流量控制只涉及到通信双方之间的收发平衡。

TCP在控制数据传输时,既要考虑接收端的接收能力,又要避免网络拥塞,因而发送方的发送窗口大小为通知窗口和拥塞窗口的最小值。

传输轮次

在TCP的拥塞避免中,我们规定:每发送拥塞窗口值个数的TCP数据段(有效数据承载),并且全部收到发送方对这些数据的ACK确认,我们就称完成了1个传输轮次

例如,拥塞窗口=4,当发送方发送了4个TCP报文段,并收到这4个TCP报文段的ACK确认,我们就称完成了一个传输轮次。

慢启动机制

慢启动通过逐步增大拥塞窗口的值来控制网络拥塞。

慢启动机制规定:

  • 拥塞窗口的初始值为1

  • 每收到一个对发出的数据段的ACK确认,便将拥塞窗口的值增加1

你懂TCP协议不,我不懂!

我们可以发现,每完成一次传输轮次,拥塞窗口的值就翻倍,即拥塞窗口随着传输轮次的增加成指数增长。

随着传输轮次的增加,拥塞窗口的值会变得很大,因此TCP拥塞控制給慢启动增加一个阈值(又称慢启动门限),当拥塞窗口>阈值时,就要进行尝试拥塞避免。

当 拥塞窗口 < 阈值 时,使用慢启动算法
当 拥塞窗口 > 阈值 时,使用拥塞避免算法
当 拥塞窗口 = 阈值时,既可以使用慢启动算法,也可时使用拥塞避免算法。

随着网络拥塞的出现和变化,阈值也会不断变化。TCP拥塞控制中,阈值的初始值为16

拥塞避免

拥塞避免算法的思路是让拥塞窗口缓慢地增大,呈线性增长,即每完成一个传输轮次,拥塞窗口增加1

拥塞避免是指在拥塞避免阶段把拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞,而不是完全能够避免拥塞。

加速递减机制

如果在使用慢启动机制或者拥塞避免机制中,发送数据时,出现了定时器超时,便执行加速递减机制:

  • 立刻将慢启动门限置为当前拥塞窗口大小的一般,然后拥塞窗口的值重置为1

  • 执行使用慢启动机制

快重传和快恢复

如果发送方设置定时器超时,那么很可能是网络出现了拥塞,致使TCP报文段在网络中的某处被丢弃。在这种情况下,TCP马上把拥塞窗口减少到1,并执行慢开始算法,同时慢开始门限值减半。这是不采用快重传机制的情况:

你懂TCP协议不,我不懂!

快重传机制要求接收方每收到一个失序的TCP报文段后就立即发出重复确认(为了使发送方及早知道没有到达对方)而不要等待自己发送数据时才进行确认。

快重传算法规定:发送方只要连续收到3个重复确认就应当立即重传未被确认的报文段

快恢复

当发送端收到连续三个重复的确认时,就执行“乘法减小”算法,把慢开始门限 ssthresh 减半。但接下去不执行慢开始算法

由于发送方现在认为网络很可能没有发生拥塞,因此现在不执行慢开始算法,即拥塞窗口 cwnd 现在不设置为 1,而是设置为慢开始门限 ssthresh 减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。