笔记整理二

                                                               TCP协议

TCP的三次握手

        链接过程中纯在状态的变化

        根据图片我们可以了解到,在TCP连接在建立的过程中,也是可以分为不同的阶段的。

        不同阶段,我们客户端或者服务器的状态都不尽相同,所以,我们使用不同的状态来进行标            识。

先从客户端的角度来看下状态的变化:

        1.一开始,在还没有发送建立请求之前,客户端处于Closed(关闭)状态。在发送完SYN请求报文段之后,将进入到下一个状态;

       2.发 送完SYN请求报文段后,客户端将处于SYN_Sent状态。在这个状态下,客户端在等待服务器返回SYN+ACK报文段;

        3.当客户端收到服务器发送的SYN+ACK报文段后,则将进入到最后的Established(建立完成)状态。因为此时客户端指向服务器的会话就已经建立好了,所以,客户端发送的最后一个ACK报文就可以携带数据了。

再从服务器的角度看下状态的变化:

        1.服务器一开始也是处于Closed(关闭)状态。当服务器的应用程序创建一个监听套接字之后,将进入到Listen(监听)状态

        2.之后,服务器将等待客户端发送的SYN请求报文段。收到后,将回复SYN+ACK报文段。回复之后将进入到SYN_RCVD状态。之后等待客户端的ACK报文。

        3.服务器接收到客户端发送的ACK报文之后,将进入到最后的状态Established(建立完成)状态。也标着着服务器指向客户端的会话建立完成。至此,整个TCP的双向会话均建立完成。

TCP的异常链接

        上面讨论的都是假定客户端和服务器都已经准备好的情况下,但如果连接情况没有那么顺利该怎么办呢?

          

        例如,假如一台主机接收到一个TCP SYN报文段,里面的目的端口号是80端口。但是,该主机80端口并不接受连接,可能是因为它并没有运行web服务器。

        在这种情况下,主机就会给发送源发一个TCP报文段,将其中的RST标记位置1。用来中断这次连接。(一般发送到一个无效的TCP连接时,都会使用RST报文段来终止)。

TCP的链接断开--TCP的四次挥手

        TCP的连接断开需要经历4次数据包的交互才能完成,所以这个过程我们习惯性的称为“四次挥手”。当然,结束连接之后,主机中的"资源"(即缓存和变量)也就被释放掉了。

        断开连接过程中的状态变化

先从客户端的角度出发,来观察下这个状态变化

        1.客户端一开始,还处于ESTABLISHED建立的状态,现在,他发现它数据传递完毕,于是发送了FIN断开请求报文段,之后,变进入到了一个新的状态 --- FIN_WAIT_1

        2.客户端再FIN_WAIT_1状态下,就是在等待服务器回复ACK,一旦等到之后,将进入到下一个状态。

        3.客户端在收到服务器发送的ACK之后进入的状态被称为FIN_WAIT_2状态。这个状态主要就是等待服务器发送FIN请求。

        4.客户端在收到服务器发送的FIN断开请求之后,将回复ACK进行确认。但注意,客户端并没有直接断开连接,进入到CLOSE(关闭)状态,而是进入到了一个TIME_WAIT状态继续等待。

        5.在这个TIME_WAIT状态等待2MSL(这是一个时间),之后进入CLOSE(关闭)状态。进入到关闭状态后客户端将释放掉所有给这个TCP连接分配的资源。

再开看下服务器方的状态变化:

        1.首先,服务器也还处在ESTABLISHED建立的状态。在收到客户端的FIN断开请求后,服务器会回复一个ACK进行确认,同时进入到CLOSED_WAIT状态

        2.这个状态随着服务器自身发送FIN断开请求报文段之后,将结束,并进入到一个新的状态-----LSAT_ACK状态

        3.从这个状态的名字就可以看出来,服务器在等待最后的一个ACK报文段。当服务器接收到这个ACK之后,则将进入最终的状态,也就是CLOSE(关闭)状态 。之后,将给这个TCP连接分配的所有资源释放掉。

以上便是整个“四次挥手”断开过程的状态变化。需要注意的就是,这里的客户端和服务器是相对的,先发送FIN的需要经历“客户端”那一套,也就是需要在关闭前进行等待。而被动方则走的是“服务器”那一套,再收到ACK报文之后直接关闭。

为什么需要有TIME_WAIT状态?

        这是很重要的一个等待,因为他是我们连接能够正常被关闭的保证

        先假设没有TIME_WAIT或者这个时间过短,如下

        从图中可以看出这个等待的必要性了。因为我们永远不能以最理想的眼光来看待网络通信。说不定,那个数据包就迷失在这网络数据之海中了。

        假设客户端发送的最后一个ACK在网络中丢失了,而我们客户端不等待或者等待时间很短,则将自顾自的关闭了TCP连接。但是,服务端没有收到最后的确认,则一直停留在了LAST_ACK状态了。这样很不负责任

        服务器长时间接收不到ACK,则也将触发重传(TCP的可靠之处了),服务器会再发送一个FIN断开请求。而此时,客户端已经关闭了该连接通道,则将回复RST来终结连接,这就属于异常的断开

        TCP确保数据可靠传输:排序 确认 重传 流控

TCP的重传机制

        发送完数据后若没有收到对方的反馈,这时就需要用到tcp的重传机制了

        RTT和RTO

        重传机制中有一个很重要的点,就在于到底多长时间没有收到你的确认报文段,我才去重传。这种重传机制我们称为超时重传。(重传不一定只是在超时的情况下出现,这个我们后面再看)

        当然,超时重传的这个时间是非常重要的一个参数,那这个时间到底应该设置为多少呢?我们需要先理解一个和时间相关的参数 --- RTT(*Round-Trip Time)往返时间

        RTT往返时间 --- 指的是发出端将数据发出后,到他接收到对端反馈的确认报文之后的这一段时间

        超时重传的这个重传时间,我们称为RTO(Retransmission Timeout 超时重传时间)。这个时间就是我们这里需要仔细关注的时间,而这个时间和我们刚才说的这个RTT往返时间是有密切关系的。  

         下图是重传时间过长过短会出现的情况:

        先看第一种情况,就是如果当超时时间比较大。其实这种情况带来的问题非常显而易见,那就是丢包之后,重传的效率会降低,无法及时响应。  

        那如果重传时间设置的过小呢?如果这个时间比较短,甚至比我们RTT时间还要短,那就有可能造成不必要的重传,可能人家也正常收到了数据包,也正常回复的确认报文,只是因为这个报文还没有到达本端,本端就重新发送一遍。这样不也是资源的浪费嘛。

        所以这个RTO的取值应该略大于RTT,但是也不能大太多。         

        这个值具体是需要通过RTT来进行计算的,并不是一个固定值。因为RTT的时间也并不都固定。至于其中的算法,我们就不去过细的研究了,我们记住这个RTO是一个动态变化的值就可以了。

        在这个超时重传中,还有一个非常有趣的机制,就是超时间隔加倍

        拿上边的图举个例子,客户端发送序列号为100的一个数据段,服务器收到后,正常的回复ACK确认报文。但遗憾的是,这个确认报文在网络传输过程中,被淹没在数据洪流中了。客户端并没有收到服务器反馈的确认报文段。于是,在等待了RTO一个超时间隔后,客户端决定重发序列号为100的这个报文段。但是,这次的情况和上次一样,服务器回复的ACK依然没能正常的到达客户端。客户端只能继续等待超时间隔之后重发。只不过,这次的超时间隔时间将是第一次的2倍。也就是说假设第一次RTO = 0.5S,则第二次的RYO = 1S。

         如果运气不好,重传的数据包还是没有收到确认的话,则这个翻倍机制将继续延续,下次的RTO = 2S了。(注意,这里都是重传同一个数据包的时候出现的情况。)  

        这个机制设计出来主要是和拥塞控制有点关系。这里的逻辑我们可以稍微盘一下。因为定时器超时导致的重传,最有可能造成这样结果的就是网络环境拥塞导致的。在这样的拥塞情况下,如果还是不停的去重传报文段,只会使拥塞加重。所以,TCP采用了这种超时间隔加倍的方式,来缓解这一点。

        

        快速重传机制

        超时重传的问题就是这个超时间隔会越来越长,这样超长时间的重传间隔,会加重端到端之间的时延。

        好在,在TCP中,发送方可以通过接收方的反馈,在超时时间到达前,意识到数据包丢失的现象发生了,并进行重传。

        这种情况出现在接受端收到一个失序报文的情况下。

        什么是失序报文呢?简单来说,就是接受方在收到一个数据段中的序列号大于自己期望的序列号,这样的报文就是一个失序报文,这就说明自己期望的报文可能在茫茫网海中丢失了。

        用上面的图再说明一下这个失序报文的问题。客户端发送了序号为100,200,300,400的报文,之后等待服务器的确认。(注意,在TCP中并不是严格的只发一个报文等待对方回复,这样效率太低,所以,我们会一次性多发几个报文再等待回复,这个发送量和窗口值有关,我们在流控的时候会详细讲解。)服务器收到序号为100的报文之后回复ACK,其中的确认序列号为200。可是,之后服务器这边并没有如愿的收到序号为200的数据报文段,而是收到了序号为300的报文段。那这种情况对于服务器来说,就是接收到了失序报文了。  

        这种情况下,服务器将意识到自己期望的报文段丢失了,所以,他不能直接确认后面的报文段,因为我们知道,TCP的确认是累计确认,也就是说,如果客户端收到服务器发送的确认序号为300的数据报文段,则客户端会任务300之前的字节流均已经传递完毕,并被接收了。则他将意识不到此时报文段丢失的问题。所以,这种情况下,服务器需要想办法让客户端知道报文段已经丢失了。但是,TCP不能直接发送一个否定确认(就是指没有办法直接发送一个数据报文段,告诉对端自己哪个报文没有接收到。)所以,TCP将采用冗余ACK(Duplicate ACK)的方式来完成这次通告。

        所谓冗余ACK就是服务器将会通过再次发送携带确认序列号未丢失报文序号的确认报文,并且连续发送三次。(如上图所示)TCP就是通过这种方式来告知对端,这个报文已经丢失了,期待对方重传。

        

        当客户端这边接收到3次冗余ACK之后,将意识到此时序号为200的报文段已经走失,需要重传。注意,这个时候的重传并不是因为RTO时间到达而触发的,这种重传是已数据包为驱动的一种重传机制,我们将这种重传称为快速重传机制。  

        这种重传会在定时器超时之前重传报文段,就可以解决超时重传时延加重的问题。

        下面的图是抓包看到的快速重传的现象图。         

TCP的流量控制

        说到TCP的流量控制,它其实应该是属于TCP可靠性保障的一环

        滑动窗口

        而要谈及流量控制,就不得不说一下TCP头部中的一个变量,那就是“窗口值”了。这个窗口值应该算是TCP实现流控的一个核心参数。

        我们知道,TCP的传输为了保证可靠性,要求每发一个数据,就要得到一次确认应答。只有收到了之前数据包的确认之后,才会发送之后的数据包。很明显,这样的传输效率并不高。如果,你发送了一个信息到对端,但是对方此时可能并没能及时处理这个信息,那你就需要一直干等。这样将导致数据包的往返时间越长,通信的效率就会越低

        所以,何不趁多方忙别的时候,自己多发几个数据包呢?为此,我们专门引入了一个变量,那就是窗口。这个窗口的大小是可以指定的,窗口大小指的就是无需等待确认应答,而可以继续发送数据包的最大值

        窗口的大小,其实体现的就是缓存区的大小。我们前面说过,TCP双方都将为这个TCP连接建立缓存区域。发送方在发送数据的时候,在没有接收到对方的确认报文之前,发送的数据需要暂时存放在缓存区。而接受方,在接收到数据来不及处理,也需要暂存在缓存区。所以,窗口的大小需要充分考虑缓存区的大小才行。

        这里我们给一个场景,假设窗口的大小是3个TCP报文段的大小。那其效果就是,发送方(我们以客户端作为发送方)可以一次性的发送三个TCP报文段,之后等待对方的确认。接收方(假定服务器方)理应针对这三个报文段进行确认,但实际上,中间的确认报文段就算丢失,也问题不大,不会触发重传,因为之前我们就说过,TCP是累计确认,只要收到最后的确认报文就相当于将前面的内容都确认了。

        

        其实窗口值的用法是比较好理解的,主要是,这个参数是如何进行流控的?前面说了,这个窗口值的大小反应的是缓存区域的大小,所以这个参数并不是一成不变的。

        TCP要求发送方依据接收窗口(receive window)--- rwnd来控制数据的发送量。这个接收窗口,其实反应的就是接受方此时缓存空间可用的大小,通过接收方发送数据包中的窗口值这个变量来携带。当然,因为TCP本身是一个全双工的通信协议,所以,通信双方都各自需要维护一个接收窗口。

         

        我们现在假设客户端给服务器发送信息(我们只看一边),假设服务器为连接分配的缓存空间用RcvBuffer来表示其大小。服务器上的进程会从这个缓存区域中读取数据。从网络中到达并放入服务器缓存空间中的数据量减去服务器进程已经读取的数据量必须要小于我们整个的RcvBuffer,才能保证缓存区不溢出。而RcvBuffer减去他们之间的差值便是rwnd。 --- 这个空间就是随时间变化的,所以,rwnd也是随时间变化的。

        服务器就是将当前的rwnd值放入发送给客户端的报文窗口字段,来通知客户端他在该连接中还有多少缓存空间可用。(开始时,rwnd == RcvBuffer)

        而客户端需要保证的就是发送的字节最大序号减去本端收到的最后确认的序号值小于rwnd。(其实就是发出还没有确认的数据量)这样才能保证服务器接收数据时不会溢出。

        也正是这样可变的窗口流控机制,我们称之为滑动窗口机制

怎么预估窗口大小这个具体值呢?

        你可以使用监控工具,比如Wireshark ,自己统计等。或者用计算的方式

        基于带宽-延迟积

        带宽-延迟积(BDP)是一个常用的计算方法,用于确定适当的TCP窗口大小。计算公式如下:

        [ \text{窗口大小(字节)} = \text{带宽(字节/秒)} \times \text{往返时间(RTT)(秒)} ]

        例如:

                带宽为100Mbps(12.5MB/s)

                RTT为50ms(0.05秒)

        计算得出:

        [ \text{窗口大小} = 12.5 \times 0.05 = 0.625 \text{MB} = 625 \text{KB} ]

        

小窗口处理

        在看TCP流控相关的文章的时候,看到了这样的一种特殊情况,很有意思。如果接受方需要处理的数据比较多的情况下,就会导致窗口值越来越小。到最后,如果接受方只能腾出几个字节的窗口,而发送方将只能发送几个字节。

        我们知道,光我们协议封装的头部,都需要占用几十个字节(比如TCP+IP头部最短就有40个字节),如果只传输几个字节的数据的话,那这个传输的经济性,则将大打折扣。

        所以,在这种情况下,应该避免接受方通告小的窗口,而发送方,也应避免发送小的数据。

        接收方一般采取的策略是设定一个窗口通告的最小值。这个最小值通常选择MSS或者1/2缓存空间这两个值中较小者。当窗口值小于其二者较小者的值时,将通告窗口值为0。直到窗口大小突破那个最小值之后,再打开窗口。

        而发送方一般的策略是启用延时处理,只有在满足以下两个条件中任意一个时,才会发送数据,否则,将一直囤积数据,直到满足任一条件为止。

        条件一:要等到窗口大小 >= MSS 并且 数据大小 >= MSS;

        条件二:收到之前发送数据的ack回包;

 

TCP的拥塞控制

        TCP的拥塞控制是TCP当中的一个难点。很多人不太理解,为什么TCP有流控,还有个拥塞控制,这两个到底有啥区别?

        通过前面我们对TCP流控的简单介绍,因该能理解,流控主要是为了防止发送方发送流量过大导致接收方缓存区溢出的问题。而拥塞控制,则主要是受网络环境的影响。TCP也会观察网络的拥堵情况,如果网络拥塞严重的话,则将降低发送量,以缓解网络拥塞情况,这种行为属于TCP的拥塞控制

        大体的思路其实比较好理解,但是这句话其实就抛出了两个问题。

        第一个问题就是,TCP是如何判断此时网络环境拥塞的;

        第二个问题就是,TCP该如何去控制数据的发送量。(准确的说,如果一个TCP发送方感知从它到目的地之间的路径上没什么拥塞,则TCP发送方增加其发送速率;如果发送方感知沿着该路径有拥塞,则发送方就会降低其发送速率。)

        

        TCP拥塞判断

        我们先来说下TCP到底是如何感知到此时网络中出现了拥塞的情况呢?

        其实拥塞判断的问题,我们前面在可靠性里面已经提及过一些。TCP将连接中出现的丢包行为,视为拥塞的表现。(这个其实很合理,一般数据包会丢失或者超时,大概率都是因为网络拥堵导致。)当然,丢包事件可以通过两种形式体现:

        其一就是数据包确认超时

        其二就是收到来自接受方发送的3个冗余ACK

        

        TCP拥塞控制

         因为发送方需要根据网络的拥塞情况来调控发送的速率,所以,发送方需要额外的维护一个变量,我们称为拥塞窗口(congestion window)--- cwnd。他和rwnd(接收窗口)一样,都可以对发送的字节数大小进行限制。也就是说,发送方发送多少数据,其实同时受rwnd和cwnd共同影响。发送方中,发出未收到确认的字节数必须小于或等于rwnd和cwnd中的最小值。所以,TCP发送方可以通过限制cwnd来间接的影响发送速率,实现调控效果。

        

        TCP拥塞控制算法

        当然,这个参数到底该如何使用,什么情况下该做什么样的调整,实际上是有专门的拥塞控制算法来进行操控的

        在了解具体的算法之前,我们需要明确一点思路,那就是太过保守的TCP传输反而会导致无法充分利用带宽。TCP被称为是自计时(self-clocking)的,就是因为他会通过确认来增大拥塞窗口。这个怎么理解呢?在不存在拥塞的情况中,TCP将收到的确认报文后会认为道路畅通,从而增加窗口的长度,其实就是在向上试探,以追求更高的传输速率。如果确认到达的慢,那么拥塞窗口的增加速率也会很慢;如果响应很及时,确认到达的很快,则拥塞窗口增加的也会很快。

        下来看下具体拥塞控制算法。这个算法主要包括三个部分 --- 慢启动,拥塞避免,快速恢复

        

        慢启动(slow - start)

        TCP的连接在刚开始建立的时候,并不会直接以很大的传输速率来工作,因为并不清楚此时的网络情况,直接大量传输那不是胡闹嘛。通常,cwnd一开始会被设置成很小的一个值,一般等同于一个MSS,即一次只能发送一个数据报文段。但这个速率需要在短时间内提升起来,这就是慢启动的过程。

        

        慢启动的做法也是非常简单的,就是每收到一个新的ACK确认报文(重传的确认不算在内),就会增加一个MSS的大小。(这就是前面说的,TCP会根据确认来增大拥塞窗口)

        所以,慢启动其实是一种快速提升传输速率的方式(不能被名字误导),因为其呈现的是指数增长。(图中拥塞窗口的数值单位为MSS)

        为了防止拥塞窗口cwnd增长过大造成网络拥塞,我们有不能一直处于慢启动状态。那他该如何停止呢?我们会设置一个慢启动门限(ssthresh)来做评定。  

                当cwnd < ssthresh时,使用慢启动算法;

                当cwnd > ssthresh时,使用拥塞避免算法;

                当cwnd = ssthresh时,既可以使用慢启动算法,也可以使用拥塞避免算法。

PS:ssthresh---慢启动阈值,一般等于当前窗口值的一半

        拥塞避免

        那下来咱们就讨论下这个拥塞避免算法是如何计算的。其实拥塞避免的思路也是很简单的,就是不能再像之前一样,高速上升了,我们需要把上升的速度降缓。以前慢启动的时候,我们是一个RTT时间内(RTT就是前面说过的往返时间,如果cwnd是4个报文段,那RTT就是发送方连续发送4个报文段之后,并收到4个确认这个过程所消耗的时间。),收到几个ACK,cwnd就增加几个MSS。而到了拥塞避免中,一个RTT时间内,cwnd就只增长一个MSS。从慢启动的指数增长变为了线性增长,增长速度明显变缓了。

        注意,还是不能被这个名字所误导,拥塞避免也不是说就可以直接避免拥塞,而是通过降低窗口的增大速率而使网络不容易出现拥塞。

        

(这里假设设定ssthresh为8)

        快速恢复

        拥塞避免的增长到啥时候结束呢?如果没有出现拥塞的情况,那最好是可以一直增加下去的。但如果出现了拥塞,那这个增长就必须停止了。

        当然,拥塞判断前面咱们说了,有两种方法,出现这两种情况,我们做出的处理方案是不一样的。

        我们可以根据这张图片来进行分析。这里我们我们设置初始的ssthresh为16个报文段。一开始,cwnd执行慢启动算法,cwnd程指数上涨,很快就来到了ssthresh的门限值。也就是图中的①点。这里将结束慢启动,开始进入到拥塞避免算法。可以看出来增长速度明显变缓。  

        变故出现再②点,假设此时的拥塞窗口是24(单位MSS),发生了超时重传的情况。这是我们判定网络出现拥塞的一种现象。这时候,我们会将ssthresh值设定为cwnd的一半(即ssthresh = cwnd / 2 = 24 / 2 = 12)。之后,将cwnd设置为1。之后,重新开始慢启动和拥塞避免的过程(他们之间的门限值就是12了。) --- 这是针对超时重传的设定。

        如果网络不会再发生拥塞,那这次的拥塞避免也应该一直执行下去,但是,变故又出现在了④点,此时cwnd是16。这时候,出现的是3次冗余ACK。这也是我们判定此时网络存在拥塞的一个依据,不过,在TCP眼中,这种情况并没有超时重传严重。因为此时并不一定网络真的存在拥塞,可能只是数据包丢失了。这时如果直接把cwnd降到1,感觉有点亏。所以,这种情况下(快速重传),我们采取的处理方案是 --- 使用快速恢复算法。

        快速恢复的做法也很简单,就是直接将ssthresh设置为cwnd的一半(即ssthresh = cwnd / 2 = 16 / 2 = 8),同时将cwnd也设置为8(相当于也降低了一半),之后,直接开始拥塞避免

        这里需要注意一下,也有快速恢复实现会把初始的cwnd值在加3个MSS长度。相当于新的cwnd初始值为ssthresh + 3。(上面例子就是8 + 3 = 11)。这样做的理由是:既然发送方收到了3个重复的确认,就证明有3个分组已经离开了网络。这三个分组不再消耗网络资源而是停留在对方缓存中,所以,并没有堆积在网络里,那就可以适当的再加大一些窗口。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值