TCP有关的资料和书籍,网上搜索恐怕汗牛充栋,我写这篇博客也是为了学习巩固,参考了《计算机网络》和goole的很多博客,毕竟站在巨人的肩膀上才能看的更高走的更远。
1.TCP协议
TCP协议属于TCP/IP协议中的传输层协议,有以下几个特点。
- TCP是面向连接的运输层协议。
- 每一条TCP连接只能点对点
- TCP提供可靠交付的服务
- TCP提供全双工通讯
- 面向字节流。
TCP是面向连接的传输层协议
TCP在传输数据前必须让接受方和发送方建立连接,然后才能传输数据,当传输完成后,要将该连接释放。就像打电话。
每一条TCP连接只能点对点
每一条TCP通过端口号来连接受方和发送方。
TCP提供可靠交付的服务(重点)
发送方通过TCP连接发送的数据,可靠,无差错,有序的发送到接受方。
TCP提供全双工通信
TCP运行通信双方在任何时候发送数据,TCP连接的两端都设有发送缓冲和接收缓存,用来临时存放双向通讯的数据。
TCP面向字节流
虽然应用程序使用TCP协议发送数据,一次发送一个大小不等的数据块,但是TCP只是把应用程序发送的数据块当成无结构的字节流,TCP不保证发送方的数据块和接受方接受的数据快大小对应,例如发送方发了10个数据块,但是接受方只用了4个数据块就把收到的字节流发给上层的应用程序(有的童学可能觉得这和TCP的安全可靠矛盾,这样岂不是丢了6个数据块,肯定不可能的,虽然数据块的个数不同,但是TCP发送的字节流是一样的)
1.TCP报文首部的格式
图片来源《计算机网络 第七版》谢希仁
首部各字段意义如下
- 源端口和目的端口号 :各占2个字节,分别写入源端口号和目的端口号。
- 序号:占4比特位,范围[0,2^32-1],序号最大后+1,编号又回到0,进行mod2 ^32操作。TCP是面向字节流的,对于TCP连接的字节流的每一个字节都按顺序编号。整个字节流的初始序号必须在连接时建立。首部的序号字段表示本报文段所发送的数据的第一个字节的序号。
- 确认号:占4比特位,请问收到的对方下一个报文段的第一个数据字节的序号。
若确认号为N,则表明:到序号N-1的字节已经成功收到。 - 数据偏移:4比特位,指出TCP报文段的数据起始到TCP报文段的起始的距离,实际上指的是首部长度,数据偏移的单位是4字节,最大能够表示的值是15,因此TCP最大的首部长度为60字节。
- 保留:占6比特位,为以后做准备,当前置为0.
- URG:URG=1时,表示系统有紧急数据,和紧急指针配合使用。
- ACK: ACK=1时,确认号字段有效,TCP规定,在连接建立后的所有传输的报文段必须把ACK置为1.
- PSH:PSH=1时,表示一端的应用进程希望在键入一个命令后立刻收到对方的响应。接受方收到PSH=1的报文,就尽快地交付交付给接收应用程序,而不是等缓存都填满后向上交付。
- 复位RST:RST=1,表示TCP中产生了严重的差错,必须释放连接,然后重新建立运输连接。
- SYN:SYN=1,作用建立连接时同步序号的,若对方同样连接则,返回的报文SYN和ACK置为1,具体在三次握手,四次挥手中详细讨论。
- FIN:FIN=1,表示此报文段的发送方的数据已经发送完毕,要求释放运输连接。
- 窗口:占2字节。值为[0,2^16-1]之间,窗口指的是发送本报文段的接收窗口,告诉对方本报文段首部的确认号算起,可以接受的最大数据量。窗口字段明确指出了允许对方发送的数据量,经常动态变化,
- 校验和:占2字节,校验首部和数据两部分。
- 紧急指针:占两字节,指出本报文段中的紧急数据的字节数。
- 选项:长度可变,最长40字节,开始的选项只有MSS,最大报文段长度,表示每一个TCP报文的数据字段的最大长度。
- MSS:和接受窗口没关系,如果选择较小的MSS会导致网络的利用率变低,如果MSS过高,在IP层传输时就有可能分解成多个短数据报片段,增大开销。MSS的默认值为536.
- SACK:选择确认,能够传输缺少的数据而不重传已经正确达到接受方的数据,在后面我们还会讲。
- 窗口扩大:为了扩大窗口,TCP首部的窗口字节长度为16,窗口扩大占3个字节,其中一个字节是S,新的窗口值为16+S,移位符的最大值为14,当连接的某一端不需要扩大窗口,可发送S=0的选项,使其窗口大小返回0.
- 时间戳:占10字节主要是时间戳值字段和时间戳回送回答字段。,用于计算往返时间,发送方把发送报文的当前时间放到时间戳中,接受方接受到后,把时间戳复制到确认报文的时间戳中,因此发送方可以计算出RTT。,还可以防止序号绕回的情况PAWS,每一个字节都有一个序号,当使用1.5M/s的速率发送报文段是,六小时后会有序号重复,可以用时间戳区分两个相同序号的字节。
以上就是TCP报文的报头看起来枯燥乏味,但是是TCP协议的基础,我们需要烂熟于心,才能理解TCP协议的实现细节。
2.可靠传输的工作原理
1.停止等待协议
TCP协议的通讯双方即是发送方又是接受方,为了讨论方便,我们只考虑A发送数据B接受数据并返回应答的情况,A为发送方,B为接受方,发送的数据单元都称为分组,“停止等待”就是每发送完一个分组就停止发送,等待对方的确认,收到确认后再发送下一个数据单元。
下面是理想状态下的无差错情况,纵轴为时间。
A发送分组M1给B,B收到后发送M2给A确认收到,A收到M2后再将下一个分组M3发给B,以此类推,这种机制叫做确认应答机制。
出现差错情况
上图是出现查重的情况,B在收到错误的报文M1后,将其丢弃并且不通知A,或者M1因为网络问题没有传输至B,这两种情况下B不发送任何信息,A只要超过一定的时间没有收到B的确认分组,就会认为前面发送的报文丢失了,会重新发送前面的分组,这种机制叫做超时重传。
为了实现这种机制,需要在每一个发送分组后设置一个超时计时器,在时间范围内,收到对方的确认,就撤销该计时器。
需要注意以下几点
- A发送完一个分组后,必须暂时保留已发送的分组的副本,只有收到确认后才能清除。
- 分组和确认分组必须进行编号,这样才能区分发送的哪个分组得到确认,哪个分组没有收到确认。
- 超时计时器设置的超时时间应该比数据在分组传输的平均往返时间多一些。有两方面考虑,假设超时时间过长,显然会降低通讯效率,但是超时时间短,会导致不必要的重传,浪费网络资源,对于超时重传的时间选择并不简单,甚至可以说是TCP最复杂的问题只一。
下面是另一种情况,B对A发送的确认丢失了,A在超时计时器的时间内没有收到确认,A会在时间到期后重传M1,B收到了这个重复的确认M1,会做两件事情
- 丢弃这个重复的M1,不向上级交付。
-
向A发送确认,虽然之前已经发送过一次,但是A又发送了一次M1,说明A没有收到M1的确认。
下面是另一种可能的情况,B对A的应答迟到了,因此A会收到重复的确认,对重复确认的处理非常简单,收下后就丢弃,在此之前B仍会收到重复的M1,和上面的操作一样,丢弃重复的M1,向A发送确认。
通常A总能受到对方发出的确认,如果A不断重复发分组却收不到确认,说明网络太差了。
上面的这种协议称为自动重传请求ARQ,意思是重传的请求是自动的,使用重传和确认机制就能在不可靠的网络上实现可靠的传输。
超时重传的时间选择
TCP采用自适应算法,它记录一个报文段从发出的时间到收到的确认的响应的时间,这两个时间的差值就是报文段的往返事件RTT。TCP保留一个RTT的加权平均往返时间RTTs。每当第一次测量RTT样本,RTTs就是该RTT样本,但是以后每次重新测量一个RTT样本就,重新计算一次RTTs。
a建议取1/8,即0.125用这种方法算出的RTTs比测量出的RTT值更加平滑。
因此超时计数器的超时重传时间RTO应该略大于上面计算出的平均往返时间RTTs,公式如下
RTTd是RTT的偏差的加权平均值,用以下公式计算,第一次测量时,RTTd为测量的RTT的一半,B推荐为1/4.即0.25。
上面的公式已经够复杂了,但是我们的任务还没完,假如我们发送了一个报文段,在超时时间范围内没有收到确认,因此我们重新发送了一个报文段,之后收到了一个确认应答,问题在于如何判定此确认报文是对先发送的报文进行确认,还是对后发送的报文进行确认。
无论怎么判断都可能产生差错,因此,TCP规定**报文段每重传一次,就把超时重传时间RTO增大一下,典型做法是取新的RTO为旧的两倍,当不发生重传时,使用上面的公式进行计算。**实际证明,这种规定比较合理。
我们介绍完了停止等待进制的基本情况,但是这个机制的最大优点是简单,缺点是信道利用率太低了,假设A和B减有一条直通的信道来传送分组。
假设A发送分组的时间是TD,TD等于分组长度除数据率,再假定分组到B后,B处理分组的时间可以忽略不记,B发送确认的时间需要TA,因此信道利用率U计算公式如下.
计算出的信道利用率是十分低下的,为了提高效率,发送方可以不使用低效率的停止等待协议,而是采用流水线传输,所谓的流水线传输就是发送方一次发送多个分组,使得信道上一直有数据一直传输。
当使用流水线传输就要使用连续ARQ协议和滑动窗口协议。
2.连续ARQ协议
下图表示发送方维持的发送窗口,它的意义为:位于发送窗口的5个分组都可以连续发出去,而不需要等待对方的确认。这样,信道利用率就提高了。
横坐标为时间坐标。连续ARQ协议规定,发送方每收到一个确认,就把发送窗口向前滑动一个单位。
接受方一般通过累计确认的方法,也就是收到几个分组后对按序到达的最后一个分组发送确认,表示之前的分组都已经正确收到了。
累计确认优点为:容易实现,即使确认丢失也不必重传,缺点是不能向发送方反映接受方已经接受的所有的分组信息。
例如,如果发送方发送前五个分组,中间第三个分组丢失,接受方只能对前两个分组发送确认,发送方无法找到后三个分组的下落,只能把后三个分组重新发一次,这种机制叫做Go-back-N(回退N),表示需要重传已经发生过的N个分组,当通信信号质量不好时,ARQ协议会带来负面影响。
3.滑动窗口协议
在ARQ协议中,我们已经说了发送窗口。如下
发送窗口后沿的后面表示已经发生且收到确认,发生窗口的前沿的前面表示没有发生的数据。发送窗口由前后沿共同确定。
发送窗口的前沿可能**向后收缩,这代表窗口变小,但是TCP强烈建议不要这么做。**因为可能发送方在收到这个通知前,已经发生了窗口的许多数据,可能会产生很多错误。
以一次发送为例子。
假定A发送了31-41的数据,窗口位置没改变,描述一个发送窗口的状态需要三个指针:P1,P2,P3.指针都指向字节序号,意义如下
- p3-p1 = A的发送窗口
- p2-p1 = 发送但未收到确认的窗口
- p3-p2 = 允许发送但未发送的数据
在图5-16中,B收到了32和33的数据,31没有收到,B只对按序收到的数据的最高序号给予确认,因此B发出的确认报文段仍为31.
假设B收到了31数据,并且把31-33的数据交给主机,B删除这些数据,接着把接受窗口向前移动3个单位,向A发送确认报文号为34,AA收到B的确认号后就把发送窗口向后滑动3个单位,P2不动,可以看到A的可用窗口增大了。
A继续发完42-53的数据后,P2和P3进行重合,由于A的发送窗口已满,停止发送数据。如果因为网络的某些原因,A没有收到B的确认报文,超过超时计数器的时间后会想ARQ机制的回退重传进制一样重新,发送报文。
窗口和缓存的关系
下方是缓冲和窗口的逻辑关系。
在这之前要明确两点
- 缓冲空间和序号空间是有限的,会循环使用。因此比起条,画出环更符合情况
- 图中的发送程序和接受程序很想我们编程时的write,read函数,但是事实上,这些函数只不过把数据拷到了系统缓存中,剩下的由协议栈来实现。
发送缓冲用来暂时存放
- 发送应用程序发给TCP准备发送的数据。
- TCP已经发生但是没有收到确认的数据。
发送窗口只是发送缓冲的一部分,已经被确认的数据应当从发送缓冲中删除,发送后沿和缓存是重合的。
接受缓存用来暂时存放
- 按序到达,但是没有被接收应用读取的数据
- 没有按序到达的数据。
- 如果收到的分组有差错,则要被丢弃,如果接收应用程序来不及读取数据,缓存最终会被填满,使得接收窗口变成0。
最后我们还要强调一下几点。
- A的发送窗口和B的接收窗口不一定相同,因为通过网络传输窗口值需要一定的时间,另外还可能根据拥塞情况减少自己的发送窗口数值。
- 对于不按序到达的数据如何处理,TCP没有规定,但是通常TCO对不按序到达的数据先放到接收窗口,等到字节流所缺少的字节收到后,再按序交付给上层的应用程序
- TCO要求接收方必须要有累积确认的功能,这样可以减少开销。接收方再合适的时候发送确认,也可以在字节有数据要发送的时候把确认信息顺便累积确认捎带,但是不应该过分推迟发送确认,TCP规定退出发送的时间不应该超过0.5秒,否则会导致发送方不必要的重传。
TCP流量控制
为什么要进行流量控制,原因是假如发送方发送的数据过块,就接收端就可能来不及接收数据,造成数据的丢失,所谓流量控制就是让发送方的发送速率不要太快,让接受方来不及接收。
下图说明如何用滑动窗口进行流量控制。
可以看到B和A之间通过rwnd的值进行流量控制,rwnd代表当前缓冲区能接收的字节量,发送方的发送窗口不能超过接受方给出的接收窗口的数值。
考虑一种情况,假设第三次B向A发送了rwnd=0的报文后,等到一段时间,B又向A发送一段rwnd=400的报文,很可惜,这个报文在网络中丢失,因此,A一直在等待B发送的报文,B也一直在等待A发送的报文,陷入相互死锁的状态。
为了解决这个问题,TCP为每一个连接设置了一个持续计数器。只要TCP连接的一方收到对方的零窗口通知,就启动,当时间到了,就发送一个零窗口探测报文段,对方给出收到零窗口探测报文时的窗口值,如果窗口表示零,那么死局就可以打破了。
TCP的拥塞控制
拥塞现象是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象。
从上面的定义看出,TCP的阻塞控制和TCP的流量控制有一定的相似处。
网络阻塞往往由许多的因素引起的,对网络的某一资源的需求超过了该资源的提供量,说人话就是,需求那么多,我想去试试。
有的人说,解决拥塞问题为什么要TCP去控制,直接把网络中的资源变多不就行了么。比如将路由器中的节点缓存容量变大,这样就能缓存更多的资源。但是你想没想过虽然节点缓存变大了,但是路由器处理数据包的速度并未发生变化,所以大量的数据就要进行排队,这时超时重传就会发生,导致的结果是节点排队的数据越来越来,直到路由器再也处理不过来就发生了死锁。又有人说将路由器处理效率提高不久可以了,这确实是一个办法,但是即便你提高了处理速度,此时网络传输的瓶颈又会被转移到其他部分,这有点像木桶原理,决定事物好坏的上限永远和事物的短板紧密相连。
流量控制关注的是端到端的控制,阻塞控制是一个全局性的过程,防止过多的数据注入网络。
下图是有关网络的数据吞吐量与造成的负载。
从上面可以看到,理想状态下,在吞吐量饱和之前,网络吞吐量应该等于提供的负载,45%的斜线,超过某个界限后,由于资源受限,吞吐量不再增长而是保持水平线,但是在实际情况下,当吞吐量未到饱和时,已经存在丢包现象了,网络就进入了轻度阻塞的状态。
原理上说,阻塞控制就是从两方面入手,增加资源,减少需求,但是不能盲目的操作,就像前面的例子一样。
事实上,拥塞控制很难设计,因为这是一个动态的问题,从大的方面有开环控制和闭环控制,开环时在设计网络时首先讲有关的因素考虑到,力图工作时不产生阻塞,一旦允许起来,就不中途改正。
闭环控制基于反馈环路的概念,检测拥塞发生的时间地点,把拥塞发送的信息传送到可采取行动的地方。调整网络系统的允许以解决出现的问题。
下面我们来介绍TCP具体的拥塞控制方法
TCP具体的拥塞控制方法
TCP进行阻塞控制的方法一共有四种慢开始,拥塞避免,快重传,快恢复
1.慢开始
在这里我们引入一个新概念拥塞窗口cwnd,拥塞窗口的大小取决于网络的拥塞程度,动态变化,发送方让自己发送窗口等于拥塞窗口。
控制拥塞窗口的原则为:只要网络没有出现拥塞,拥塞窗口就可以再增大,提高网络的利用率,只要网络受到阻塞,拥塞窗口就会变小,缓解网络出现的阻塞。
发送方怎么判断是否发生拥塞?
当网络发送拥塞,就会产生丢包,网络就可能发生拥塞,,其他原因产生的丢包的概率极小,因此,对于发送方来说超时就是判断网络产生拥塞的原因。
慢开始的思路是这样的,开始时从小到大逐渐增加发送窗口,也就是从小到大增加拥塞窗口数值。
具体的操作为,开始发送报文段时,先把初始拥塞窗口cwnd设置为2到4个SMSS(最大报文段),并且有如下规定。
- SMSS>2190 cwnd = 2*SMSS,不能超过两个报文段
- SMSS=2190 cwnd = 3*SMSS,不能超过三个报文段
- SMSS<2190 cwnd = 4*SMSS,不能超过四个报文段
满开始规定,没收到一个新的报文段确认后,就可以把拥塞窗口增加一个SMSS的数值。
下面用例子来说明。
我们从上面可以看出cwnd,每次发送后都会翻倍,1-2-4-8-…,指数级增长。
所以我们要特别指出慢开始的“慢”不是指速率,而是指TCP开始发送报文段时先设置cwnd=1,然后再逐渐增大cwnd,这比一开始就传入大量的数据要“慢”的多。
为了防止cwnd增长过快,引起网络拥塞,需要设置一个值慢开始门限,来确定当前使用的算法。
比较cwnd和慢开始门限的大小,如果cwnd相对较小使用慢开始算法,否则使用拥塞避免算法。
2.拥塞避免
拥塞避免的思路是让拥塞窗口缓慢增大,每经过一个往返时间,把发送方的拥塞窗口cwnd+1.以下面为例。
本例中门限值为16,cwnd值为1,在点1之前,用慢开始算法,cwnd成指数增加,到了点1,使用拥塞避免算法,每次轮次后cwnd+1,到了点2,发现超时,说明产生了拥塞,于是调整门限值为拥塞的cwnd/2=12,cwnd=1,到了点3新的门限值,再次由慢开始,变成了拥塞算法。
到了点4有了新的情况,发送方收到了3个相同的ACK,这是因为TCP的快重传算法,需要执行快恢复算法,调整门限值为当前值/2=8,同时设置cwnd=8,执行拥塞算法。
3.快重传和快恢复
采用快重传算法,可以让发送方尽早发现个别报文段的丢失,快重传要求不能自己发送数据时捎带应答,而是立即应答,即使是别的报文段也要应答之前收到的报文段,具体如下。
根据,快重传规定,接收方只要收到三个重复的确认,就要立刻重传缺失的文件。
有的同学想问,既然有了这么方便的快重传,还要超时重传干什么?假设有一种极端情况,因为网络问题,接收端一直收不到对面发出的确认。所以超时重传是快重传的最后底线。
快恢复算法有的是将当前门限值/2,并且让cwnd = 当前门限值,有的则是让拥塞窗口cwnd+3.
综上所述,TCP拥塞控制的流程图如下
所以拥塞避免有两条路可走,超时重传+慢开始,快重传+快恢复。
总结
- TCP是传输层的协议,五大特点。
- TCP可靠传输的工作原理是停止等待协议,连续ARQ协议,滑动窗口协议。
- TCP报文组成
- TCP的流量控制
- TCP的拥塞控制。
上面是TCP协议的重点,希望读者每个知识点都能记住,这里特别推荐谢希仁老师的《计算机网络》,上面的大部分知识,和所有的图都来自该书中。
对于TCP三次握手和四次挥手,因为特别重要,因此需要再开一个博客来说明,希望看这篇博客的读者能和我共同进步。