15.TCP 报文头部的格式,字段的意义。
TCP由IETF的RFC 675、RFC 793、RFC 1122、RFC 2581和RFC 5681描述。
TCP基本概念中需要注意的一些问题:
TCP连接是一条虚连接而不是一条真正的物理连接。
TCP根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节
(UDP 发送的报文长度是应用进程给出的)。
TCP对应用进程一次把多长的报文发送到TCP的缓存中是不关心的。
TCP可把太长的数据块划分短一些再传送。TCP也可等待积累有足够多的字节后再构成报文段再传送。
TCP头部的格式如下图:
源端口和目的端口字段 各占 2 字节。端口是运输层与应用层的服务接口。运输层的复用和分用功能都要通过端口才能实现。
seq序号字段 占 4 字节。TCP连接中为传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。
ack确认号字段 占 4 字节,是期望收到对方的下一个报文段的第一个字节的序号。
数据偏移(首部长度) 占4位,指出数据起始处距离整个报文段的起始处的偏移。单位是4字节。
保留字段 占6位,保留为今后使用,但目前应置为0。
URG紧急 当URG = 1时,表明有紧急数据,此时紧急指针字段有效,应尽快传送(相当于高优先级)。
ACK确认 只有当 ACK = 1 时确认号字段才有效。当 ACK = 0 时,确认号无效。
PSH推送 接收端收到PSH = 1的报文,就尽快地交付接到的数据,不再等到缓存完全填满再向上交付。
RST复位 当RST = 1时,表明出现严重差错(如主机崩溃),必须释放连接,再重新建立连接。
SYN同步 当SYN = 1表示这是一个连接请求或连接接受报文。
FIN终止 当FIN = 1时表明此报文段的发送端的数据已发送完毕,并要求释放连接。
窗口 占2字节,用来让对方设置发送窗口大小的依据,单位为字节。
检验和 占2字节。检验范围包括首部和数据。计算时要在TCP报文段的前面加上12字节的伪首部。
紧急指针 占16位,指出本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)。
选项与填充 长度可变。两个字段长度之和为4字节的整数倍。常见选项如下:
MSS最大报文段长度选项 本字段用来通知对方,本方缓存所能接收数据的最大长度是MSS字节。
窗口扩大选项 占3字节,其中有一个字节表示移位值 S。新的窗口值等于TCP首部中的窗口位数增大到(16 + S),相当于把窗口字段的值向左移动S位后即可获得实际的窗口大小。
时间戳选项 占10字节,其中最主要的字段时间戳值字段(4字节)和时间戳回送回答字段(4字节)。
SACK选择确认选项 Selective Acknowledgment,向对方汇报收到的数据碎片,进行选择性的确认。
16.TCP通过哪些措施保证传输可靠?确认和重传机制。
可靠传输的基本思想:发送方每次发送一个报文段,接收方都要予以确认。如果发送方没有收到接收方返回的确认信息,则必须重新发送此报文段。直至发送方收到了对方传来的确认信息才停止重新发送。
可靠传输的工作原理(滑动窗口协议与停止等待协议的区别):
(1)停止等待协议中的四种情况,每次传送一个分组,并等待确认。得到确认后继续发送下一个分组。
(2)连续ARQ(Automatic Repeat-reQuest)协议,创建一个滑动窗口并负责维护。滑动窗口的接收方一般采用累积确认的方式。即不必对收到的分组逐个发送确认,而是对按序到达的最后一个分组发送确认,这样就表示:到这个分组为止的所有分组都已正确收到了。这样做可以提高确认的效率。
TCP可靠通信的具体实例:
数据传输中的Sequence Number与Acknowledge number, ack采用累计确认。仅展示正常情况。
确认过程主要使用了头部中的seq序号字段和ack确认号字段,另外出现了SYN,ACK和PSH标志。
17.超时重传的时间的设定,RTT的算法。
TCP每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的重传时间到但还没有收到确认,就要重传这一报文段。其中重传时间的设置会有以下问题:
如果设置的太长,重发就慢,丢失很久才重发,没有效率,性能差;
如果设置的太短,可能并没有丢就重发。会增加网络拥塞,导致更多的超时,进而导致更多的重发。
因此重传时间在不同的网络的情况下,只能动态地设置。
为了动态地设置,TCP引入了RTT(Round Trip Time)的概念,也就是一个数据包从发出去到回来的时间。这样发送端就大约知道需要多少的时间,从而可以方便地设置RTO(Retransmission Time Out)。简单的来说,就是在发送端发包时记下t0,然后接收端再把这个包的ack传回时再记下t1,于是RTT = t1 - t0。但这只是一个采样,并不能代表普遍情况。实际的实现是一个很复杂的问题。
经典算法
RFC793中定义了经典算法Exponential weighted moving average(指数加权移动平均):
1)首先,先采样RTT,记下最近几次的RTT值。
2)然后做平滑计算SRTT(Smoothed RTT)。公式为:
SRTT = ( α * SRTT ) + ((1- α) * RTT) (其中的 α 取值在0.8 到 0.9之间)
3)开始计算RTO。公式如下:RTO = min [ UBOUND, max [ LBOUND, (β * SRTT) ] ]
其中:UBOUND是timeout时间的上限值,LBOUND是下限值。β值一般在1.3到2.0之间。
Karn / Partridge 算法
上面的算法在重传时会出现一个问题:是用第一次发数据的时间和ack回来的时间做RTT样本值,还是用重传的时间和ack回来的时间做RTT样本值?
问题如下图所示:
(a)ack没传回来,所以重传。如果计算第一次发送和ack之间的时间,则RTT被高估。
(b)ack回来晚了并触发重传,但很快之前ack到达。如果计算重传和ack的时间差,则RTT被低估。
1987年的时候,出现了Karn / Partridge Algorithm,这个算法不把重传的RTT做采样。
但是如果在某一时间,网络突然变慢,产生较大的时延,这个时延导致要重传所有的包(因为之前的RTO很小),又因为不计算重传,所以RTO就不会被更新。这是一个灾难。于是Karn算法规定只要一发生重传,就对现有的RTO值翻倍(Exponential backoff,即指数退避)。但此算法中简单的RTO翻倍并不能解决如何正确更新RTT的问题。
Jacobson / Karels 算法
前面两种算法用的都是"加权移动平均",这种方法最大的问题就是如果RTT有一个大的波动的话,很难被发现,因为被平滑掉了。所以1988年又提出Jacobson / Karels Algorithm。这个算法引入了最新的RTT的采样和平滑过的SRTT做因子来计算。公式如下:
1)计算SRTT = SRTT + α (RTT – SRTT);(其中 α = 0.125)
2)计算SRTT和真实的差距(其中DevRTT即RTT的标准差)
DevRTT = (1-β)*DevRTT + β*(|RTT-SRTT|);(其中 β = 0.25)
计算RTO= µ * SRTT + ∂ *DevRTT;(其中μ = 1,∂ = 4)
最后的这个算法在被用在今天的TCP协议中。(关于参数:nobody knows why, it just works…)
18.快速重传机制与SACK、D-SACK
TCP还有一种叫Fast Retransmit(快速重传)的算法,算法不以时间驱动,而以返回的ack数据驱动。具体来说,如果发送方连续收到3次相同的ack(Duplicated ACKs),就认为此包已丢失并立即重传,而不等计时器超时。
比如:如果发送方发出了1,2,3,4,5份数据,1到达后返回ack=2,2因为某些原因没收到,之后3到达了并返回ack=2,后来4和5都到了,但还是返回ack=2,此时发送端收到了三个ack=2的确认,于是就立即重传2。后来接收端收到了2,因为3,4,5也都收到了,于是返回ack=6。示意图如下:
快速重传和超时重传都没有解决一个问题,下面是对此问题的一个描述:
发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2并返回ack=3,然后收到了4和5,由于应当确认最大的连续收到的包,则接收端依然返回ack=3。对此有两种选择:
(1)乐观的做法是,仅重传最后一个ack的包。也就是第3份数据。
(2)悲观的做法是,重传最后一个ack包和之后的所有数据,也就是第3,4,5这三份数据。
第一种会节省带宽,但是效率低;第二种效率会有所提高,但是会浪费带宽做无用功。
SACK方法(Selective Acknowledgment)
SACK是解决上述问题的一种方式,这种方式需要在TCP头部加入可选字段SACK,ACK不发生变化。这样发送端就可以根据传回的SACK来确定哪些数据未按序到达。这个协议需要两边同时支持。
而SACK则是汇报收到的数据碎版。参看下图:
这里还需要注意一个问题,即接收方Reneging(违约),意思是接收方有权将已经报给发送端SACK的数据丢弃。这样做是不被鼓励的,因为问题会变得复杂。但接收方可能会在某些极端情况下这么做,比如要把内存给更重要的东西。所以发送方也不能完全依赖SACK。发送方最终还是要依赖ack信息,并维护Time-Out。如果后续的ack依然没有增长,那么还是要把SACK的部分重传。另外,接收端这边永远不能把SACK部分的包标记为ack。
SACK也会消耗发送方的资源,如果一个攻击者向数据发送方发送一堆无意义的SACK选项,这会导致发送方要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源。
Duplicate SACK 重复SACK,又称D-SACK,主要是接收方使用SACK来告诉发送方有哪些数据被重复接收了。D-SACK使用了SACK的第一个段来做标志,其中有两条规则:
如果SACK的第一个段的范围被ACK所覆盖,那么就是D-SACK
如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就是D-SACK
示例一:ack包丢失。示例中丢失了两个ack,所以发送端重传了第一个数据包(3000-3499),接收端发现重复收到,于是回复SACK=3000-3500,因为ack已经到了4000,即意味着收到了4000之前的所有数据,所以这个SACK就是D-SACK。接收端通过此种方式告诉发送端收到了重复的数据,而且发送端还会知道,D-SACK确认部分的数据包没有丢,丢的是回复的ack包。
示例二:网络延误。网络包(1000-1499)被网络给延误了,导致发送端没有收到ack,而后面到达的三个ack包触发了发送端的快重传",但重传时延误的包又到了,所以接收端回复了SACK=1000-1500,因为ack=3000,所以这个SACK是D-SACK。发送端因此得知之前因为快重传算法触发的重传不是因为发出的包丢了,也不是因为回应的ack包丢了,而是由于网络延时导致了包失序(Reordering)。
可见,引入了D-SACK可以使TCP进行更好的流控,具体来说有以下几个好处:
1)可以让发送方知道,是发送的包丢了,还是返回的ACK包丢了;
2)网络上是否出现了包失序;
3)数据包是否被网络上的路由器复制并转发了。
19.TCP的流量控制(Flow Control):滑动窗口机制与现实细节。
一般的发送过程与接收过程(滑动窗口包括双方的发送窗口与接收窗口共计4个窗口。)
(1)发送方A根据接收方B提供的窗口信息(TCP头部中的窗口字段,又叫Advertised-Window)构造自己的发送窗口。(TCP标准强烈不赞成发送窗口的前沿向后收缩)
(2)发送方发送了一些数据,并在随后收到了新的确认信息(窗口信息维持不变)。
(3)A 的发送窗口内的序号都已用完,但还没有再收到确认,必须停止发送。
滑动窗口与缓存的关系(A的发送窗口并不总是和B的接收窗口一样大,因为有一定的时间滞后):
发送缓存中存放发送方应用程序传给发送方TCP待发送的数据和TCP已发送出但尚未收到确认的数据。
接收缓存中存放按序到达但尚未被接收方应用程序读取的数据和未按序到达的数据。
零窗口问题的出现与解决方式:
上例中展示了一个处理缓慢的Server(接收端)是怎么把Client(发送端)的TCP Sliding Window给降成0的。此时发送端就不发数据了,可以想像成窗口被关闭了。
一段时间后,接收方Window size不为0时,怎么通知发送端呢?
TCP使用了Zero Window Probe(零窗口探测)技术解决这个问题,缩写为ZWP。发送端在窗口变成0后会发ZWP的包给接收方,让接收方来ack他的窗口尺寸。一般这个值会设置成3次,每次大约30-60秒。如果3次过后还是0的话,发送方就会发RST把链接断了。(不同的实现可能会不一样)
注意:只要有等待的地方都可能出现DDoS攻击,Zero Window也不例外,一些攻击者会在和HTTP建好链发完GET请求后,就把Window设置为0,然后服务端就只能等待进行ZWP,于是攻击者会并发大量的这样的请求,把服务器端的资源耗尽。
传输效率问题与糊涂窗口综合症Silly Window Syndrome
如果接收方太忙,那么就会导致发送方窗口越来越小甚至发生零窗口的情况。此时如果接收方在读取几字节的信息后,向发送方通知了一个很小的窗口值,那么发送方是否应该立即发送这几个字节?要知道, TCP+IP协议的头共有40字节,为了几字节的数据信息,花这么大的开销显然是不经济的。
每个网络上有会有一个MTU,以太网的MTU一般是1500字节,除去TCP/IP头部的40字节,数据部分可传输的有1460字节,这就是MSS(Max Segment Size)。如果网络包可以塞满MTU,那么对带宽的利用率就可以达到最高。可以将MTU想像成一架飞机,如果一班飞机满载则带宽最高,如果只运一名乘客则开销最大。糊涂窗口综合症就是指像一班可以载200人的飞机只坐了几个人的情况。
解决糊涂窗口综合症,就需要控制 TCP报文段的发送时机,一般有以下三种机制:
(1)只要缓存中存放的数据达到 MSS字节时,就组装成一个 TCP 报文段发送出去;
(2)发送方的应用层程序指明要求发送报文段,即使用PSH字段进行推送操作;
(3)发送方的一个计时器到时,就把当前的缓存数据装入报文段发送出去。
20.TCP的拥塞处理(Congestion Handling)原理与算法。
TCP通过滑动窗口机制实现了流量控制,但滑动窗口仅依赖于连接的发送端和接收端,并不知道网络中间发生了什么。但TCP的还应该地知道整个网络上的事。
提出拥塞控制的原因:在某段时间,若对网络中某资源的需求超过了该资源所能提供的可用部分,网络就会产生拥塞。举例来说,如果网络上的延迟突然增大导致丢包,RTO时间到之后TCP进行重传,但重传会导致网络负担更重,于是会导致更大的延迟以及更多的丢包。这就会进入恶性循环。试想如果网络内有成千上万的TCP都如此工作,那么TCP这个协议本身就很容易引起灾难。
因此TCP不能忽略网络情况而只是简单的进行重传。TCP的设计理念是:TCP不是一个自私的协议,当拥塞发生的时候,要做自我牺牲。就像交通阻塞一样,每辆车都应该把路让出来,而不是继续抢路。
拥塞控制的作用与目标:
拥塞控制的实现:为实现拥塞控制,需要由发送方维护一个叫做拥塞窗口cwnd (Congestion Window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方应当让自己的发送窗口不大于拥塞窗口,比如需要考虑到接收方的窗口大小,则发送窗口可能小于拥塞窗口。
发送方控制拥塞窗口的基本原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去;但只要网络出现拥塞,拥塞窗口就减小一些,以减少发送到网络中的分组数。
拥塞控制主要由四个算法组成:1)慢启动,2)拥塞避免,3)拥塞发生时的处理,4)快速恢复。
这四个算法的发展经历了很多时间,到今天都还在优化中。其中:
1988年,TCP Tahoe 提出了1)慢启动,2)拥塞避免,3)拥塞发生时的处理
1990年,TCP Reno 在Tahoe的基础上增加了4)快速恢复
慢启动(Slow Start)。算法的思路是,新建立的TCP连接应当缓慢增大窗口。慢启动的算法如下:
1)连接建立后初始化cwnd = 1,表示可以传送一个MSS的数据。
2)每收到一个ACK,cwnd++(自增1),此过程中cwnd呈线性上升;
3)每经过一个RTT,cwnd = cwnd*2(翻倍),此过程中cwnd呈指数上升;
4)当cwnd的值大于等于ssthresh(Slow Start Threshold)后,就进入"拥塞避免算法"。
在网络速度很快的情况下,ack返回的快,RTT也会很短,慢启动也不会很慢。下图说明了这个过程:
拥塞避免 (Congestion Avoidance)。当cwnd >= ssthresh时,就会停止使用慢启动算法,并进入拥塞避免算法。拥塞避免算法的思路是让cwnd缓慢地增大,即每经过一个RTT后只把cwnd加1而不是翻倍,从而使cwnd线性缓慢增长。一般来说ssthresh的值是65535是字节,注意拥塞窗口实际的大小是cwnd * MSS个字节。拥塞避免算法如下:
1)当收到一个ACK时,cwnd = cwnd + 1 / cwnd;
2)每经过一个RTT时,cwnd = cwnd + 1。
这样可以避免cwnd增长过快导致网络拥塞,缓慢的增加cwnd,逐渐调整到网络容许的最佳值。
发生拥塞后的处理方式:无论在慢启动阶段还是在拥塞避免阶段,只要发送方在RTO超时还没有收到ACK,或是连续收到了3个相同的ack,就认为网络出现了拥塞。对这两种情况会区别对待:
(1)RTO 超时的情况。TCP认为这种情况非常严重,反应也非常强烈。具体做法是:
1)把ssthresh设为出现拥塞时cwnd的一半,即sshthresh=cwnd*MSS/2,但不小于2*MSS;
2)把cwnd重置为1,并进入慢启动过程。
这样就会使主机发出分组迅速减少,使发生拥塞的路由器有足够时间把队列中积压的分组处理完毕。
(2)连续收到三个相同ack的情况。TCP认为这种情况不是非常严重,因为既然有3个重复ack,那么说明网络情况不是特别糟,所以没有必要像RTO超时一样反应强烈,最新的TCP Reno的做法是:
1)把ssthresh设为出现拥塞时cwnd的一半,与RTO超时情况的第一步相同;
2)发送方认为现在网络很可能并未发生拥塞,因此现在不执行把cwnd置为1并执行慢开始算法,而是把cwnd置为ssthresh,即cwnd = ssthresh,并在之后进入拥塞避免算法。
下面是两种情况的对比:
快速恢复算法(Fast Recovery)
快速恢复算法一般和快速重传同时使用。首先将cwnd 和sshthresh按前述规则更新,即:
sshthresh = cwnd / 2; cwnd = sshthres;
然后进入快速恢复算法,而不是简单的进入拥塞避免算法。快速恢复算法的描述如下:
(1)cwnd = sshthresh + 3 * MSS (3是指有3个数据包被收到了);
(2)重传D-ACKs指定的数据包。如果只丢了一个包,那么返回的ack会确认最后一个已发送的包;如果没有对最后一个已发送的包进行确认,则说明有多个包丢了,这个ACK称为Partial ACK,一旦出现P-ACK,就要重传所有已发送且未被确认的包,直到不再收到P-ACK。
在快速恢复算法结束后,TCP才会再进入拥塞避免算法。
此外还可以使用SACK和D-SACK提供的信息,对快速恢复算法中的拥塞窗口进行更好的控制。其中FACK(Forward Acknowledgment)算法就是基于SACK的,但是SACK需要TCP两端的同时支持。
第一部分:计算机网络知识点复习(一)
第三部分:计算机网络知识点复习(三)