zoukankan      html  css  js  c++  java
  • TCP常见的定时器及三次握手与四次挥手

    1.TCP常见的定时器

    在TCP协议中有的时候需要定期或者按照某个算法对某个事件进行触发,那么这个时候,TCP协议是使用定时器进行实现的。在TCP中,会有七种定时器:

    重传定时器(retransmission timer)

    坚持定时器(persist timer)

    保活定时器(keepalive timer)

    TIME_WAIT定时器 (TIME_WAIT timer, 也叫2MSL timer)

    FIN_WAIT_2定时器(FIN_WAIT_2 timer)

    延迟应答定时器(delayed ACK timer)

    建立连接定时器(connection-establishment timer)

    等待时间RTT:发送一个数据包到收到对应的ACK,所花费的时间。即一个给定连接的往返时间。由于网络流量变化,这个时间会相应地发生改变,TCP需要跟踪这些变化。RTT由三部分组成:链路的传播时间,末端系统的处理时间,路由器缓存中的排队和处理时间。

    其中,前两个部分的值对于一个TCP连接相对固定,路由器缓存中的排队和处理时间会随着整个网络拥塞程度的变化而变化。所以RTT的变化在一定程度上反应了网络的拥塞程度。

    RTT的值应该动态计算。公式是:RTT=previous RTT*alpha + (1-alpha)*current RTT。这是一个平滑的RTT又称置为SRTT, alpha是一个平滑因子,取值为0.8或者0.9,也可以说新的RTT是以前的RTT值的90%加上当前RTT值的10%.

    Karn算法:对重传报文,在计算新的RTT时,不考虑重传报文的RTT。因为无法推理出:发送端所收到的确认是对上一次报文段的确认还是对重传报文段的确认。干脆不计入。

    (1)重传计时器:

    重传定时器:为了控制丢失或丢弃的报文段,即控制报文段确认的等待时间。当TCP发送报文段时,就创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认ACK,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重新发送队列中需要重传的报文段,并把计时器复位;规则:

    1. 当TCP发送了位于发送队列最前端的报文段后就启动这个RTO计时器; 
    2. 如果队列为空则停止计时器,否则重启计时器; 
    3. 当计时器超时后,TCP会重传发送队列最前端的报文段; 
    4. 当一个或者多个报文段被累计确认后,这个或者这些报文段会被清除出队列

    RTO: 发送数据包,启动重传定时器,到重传定时器被重置所花费的时间,称为RTO。RTT会由于网络流量的变化,而相应地发生改变,所以TCP需要跟踪这些变化并动态调整超时时间RTO。可简记为重传时间RTO=2*RTT;

    基于RTT,计算出对应的RTO

         RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]

    其中UBOUND是最大值,一般情况下为120s,LBOUND是最小重传值,一般情况下为1s,Beta取值为1.3~2.0. [RFC793]

    Jacobaon/Karels 算法

    第一次RTO计算方法, 假设RTT = R

    1.  SRTT = R

    2.  RTTVAR = R/2

    3.  RTO = SRTT + max(G, K*RTTVAR) , K = 4

    后续的RTO计算,假设当前的RTT为R'

             RTTVAR = (1 - beta)*RTTVAR + beta*|SRTT - R'|    *计算平滑RTT和真实RTT的差距,切记这个地方的SRTT是上一次的SRTT*

    SRTT = (1 - alpha)*SRTT + alpha*R' * 计算平滑RTT* 

     RTO = SRTT + max(G, K*RTTVAR)

    alpha = 1/8  beta = 1/4,  值得指出的是这个算法在目前的Linux协议栈中应用,多么伟大的一件事情。

    (2)坚持定时器

    坚持定时器是在一方滑动窗口为0之后,另外一方停止传输数据,进入坚持定时器的轮询,直到滑动窗口不再为0。主要是解决0窗口可能导致的问题。

    场景:刚开始接收端向发送端发送一个零窗口报文段。不久后,如果接收端的缓存区有空间可以接收数据,则会向发送端发送非零窗口大小的报文段(即窗口更新),但报文段在传输过程中丢失,结果是发送端没有收到该非零窗口的报文段而一直处于等待非零窗口的报文端通知中,而由于接收端已经发送此报文段且并不知道该报文段丢失,则接收端会一直处于等待接收数据状态,如果没有任何措施的话,这个死锁的局面会一直延续下去。

    解决:TCP 为每一个连接设有一个坚持定时器(也叫持续计数器)。当发送端收到零窗口的确认时就启动坚持计时器,当坚持计时器到期时就发送一个探测报文段,这个报文段只有一个字节的数据,且有序号,但序号永远不需要确认,在计算对其他部分数据的确认时这个序号也被忽略。探测报文段提醒接收端,确认已丢失,须重传。计时器的截止期即为重传时间的值,若无响应,则再次发送探测报文段,并将坚持计时器的值加倍和并复位,如此直到这个值增大到阈值为止(通常为 60 秒)。在此之后,发送端每隔 60s 就发送一个报文段,直到窗口重新打开为止。

    说说术语,首先是滑动窗口,可以简单理解为缓冲区剩余空间大小。不管是写缓冲还是读缓冲,一旦一方通告了自己的滑动窗口大小,另外一方就会根据滑动窗口大小传递窗口大小的数据了。但是,当被通告,一方的滑动窗口大小为0的时候,另外一方就会启动坚持定时器,基本也是使用TCP指数退避方法,第一次1.5秒,第二次1.5x2秒,第三次1.5x4...

    其次是糊涂窗口综合症。这个症状是滑动窗口引起的。病因是发送方和接收方在一个很小的滑动窗口的时候就开始数据传输,传输结束之后,读写的消费速度也并没有那么快,导致下次传输的时候,滑动窗口还是那么小。然后现象就是每次传输的数据都非常小。就好比每次开出去的火车载货量只有一节车厢,其实我们是希望能攒够n节车厢才开始传输。

    糊涂窗口综合症有解决办法,还不止一种,在接收方或者发送方都可以解决。大致就是如果接收方解决,那么接收方在接收窗口小于一定大小的时候,对所有的接收请求都返回窗口为0的包,来触发另外一方的坚持定时器。同样发送方也是,在可以发送的数据大于一定窗口的时候才发送。

    (3)保活定时器

    这个就是我们经常说的tcp的keepalive了。实际使用场景是在应用层没有数据进行传输的时候,一定时间(tcp_keepalive_time,默认每2个小时)发送一次保持心跳的包,如果发送成功,则继续保持端口活跃,如果没有正常返回,则在指定次数内(tcp_keepalive_probes,默认是9次),指定间隔(tcp_keepalive_intvl,默认是75s)发送心跳包。如果最后都没有获得正常的ACK,那么才算连接失败。

    当然,tcp是否需要提供keepalive机制,是有争议的,我们可以为每个tcp连接设置是否启用keepalive和启用keepalive的各个指标设置。

    (4)TIME_WAIT定时器

    TIME_WAIT是主动关闭连接的一端最后进入的状态, 而不是直接变成CLOSED的状态, 为什么呢?第一个原因是万一被动关闭的一端在超时时间内没有收到最后一个ACK, 则会重发最后的FIN,2MSL(报文段最大生存时间)等待时间保证了重发的FIN会被主动关闭的一段收到且重新发送最后一个ACK;另外一个原因是在2MSL等待时间时,任何迟到的报文段会被接收并丢弃,防止老的TCP连接的包在新的TCP连接里面出现。不可避免的,在这个2MSL等待时间内,不会建立同样(源IP, 源端口,目的IP,目的端口)的连接。

    (5)FIN_WAIT_2定时器

    主动关闭的一端调用完close以后(即发FIN给被动关闭的一端, 并且收到其对FIN的确认ACK)则进入FIN_WAIT_2状态。如果这个时候因为网络突然断掉、被动关闭的一段宕机等原因,导致主动关闭的一端不能收到被动关闭的一端发来的FIN,主动关闭的一段总不能一直傻等着,占着资源不撒手吧?这个时候就需要FIN_WAIT_2定时器出马了, 如果在该定时器超时的时候,还是没收到被动关闭一端发来的FIN,那么不好意思, 不等了, 直接释放这个链接。FIN_WAIT_2定时器的时间可以从/proc/sys/net/ipv4/tcp_fin_timeout中查看和设置。

    (6)建立连接定时器

    顾名思义,这个定时器是在建立连接的时候使用的, 我们知道, TCP建立连接需要3次握手, 如下图所示:

    建立连接的过程中,在发送SYN时, 会启动一个定时器(默认应该是3秒),如果SYN包丢失了, 那么3秒以后会重新发送SYN包的(当然还会启动一个新的定时器(设置成6秒超时),当然也不会一直没完没了的发SYN包, 在/proc/sys/net/ipv4/tcp_syn_retries 可以设置到底要重新发送几次SYN包。

    (7)延迟应答定时器

    延迟应答也被称为捎带ACK,这个定时器是在延迟应答的时候使用的。为什么要延迟应答呢?延迟应答是为了提高网络传输的效率。

       举例说明,比如服务端收到客户端的数据后,不是立刻回ACK给客户端,而是等一段时间(一般最大200ms),这样如果服务端要是有数据需要发给客户端,那么这个ACK就和服务端的数据一起发给客户端了,这样比立即回给客户端一个ACK节省了一个数据包。

    参考资料

    《TCP/IP协议详解》
    《高效TCP/IP编程》

    2.三次握手与四次挥手

    (1)什么是三次握手

      “三次握手”即对每次发送的数据量进行跟踪与协商使数据段的发送和接收同步,根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系,并建立虚连接。

    下面详细解释,如图为“三次握手”的过程:

    建⽴连接的过程:

    1)第一次握手:客户端发出段1,SYN位表⽰连接请求。序号是1000,这个序号在⽹络通讯中⽤作临时的地址,每发⼀个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况,另外,规定SYN位和FIN位也要占⼀个序号,这次虽然没发数据,但是由于发了SYN,因此下次再发送应该⽤序号1001mss表⽰最⼤段尺⼨, 如果⼀个段太⼤,封装成帧后超过了链路层的最⼤帧长度,就必须在IP 层分⽚,为了避免这种情况,客户端声明⾃⼰的最⼤段尺⼨,建议服务器端发来的段不要超过这个长度。

    客户端发送完数据包之后进入SYN_SENT状态,等待服务器确认。

    2)第二次握手:服务器发出段2,也带有SYN,同时置ACK位表⽰确认,确认序号是1001,表⽰我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出⼀个连接请求,同时声明最⼤尺⼨为1024

    此时服务器进入SYN_RECV状态

    3)第三次握手:客户端发出段3,对服务器的连接请求进⾏应答,确认序号是8001

    此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

      在这个例⼦中,⾸先客户端主动发起连接、发送请求,然后服务器端响应请求,随后建立连接成功。两条竖线表⽰通讯的两端,从上到下表⽰时间的先后顺序,注意,数据从⼀端传到 ⽹络的另⼀端也需要时间,所以图中的箭头都是斜的。双⽅发送的段按时间顺序编号为1-10, 各段中的主要信息在箭头上标出,例如段2的箭头上标着SYN, 8000(0), ACK 1001, <mss 1024>, 表⽰该段中 的SYN位置1,32位序号是8000,该段不携带有效载荷(数据字节数为0),ACK位置1,32位确认 序号是1001,带有⼀个mss选项值为1024

     在这个过程中,客户端和服务器分别给对⽅发了连接请求,也应答了对⽅的连接请求,其中服务器的请求和应答在⼀个段中发出,因此⼀共有三个段⽤于建⽴连接,称为'''三⽅握⼿。在建⽴连接的同时,双⽅协商了⼀些信息,例如双⽅发送序号的初始值、最⼤段 尺⼨等

    (2)3次握手过程中的状态:

    LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。通俗的说就是收服务器可以开始服务了。

    SYN_SENT: 当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。(客户端)

    SYN_RCVD: 这个状态与SYN_SENT遥想呼应这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个 ACK报文不予发送。(服务器端)服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于 SYN_RECV状态,当服务器收到客户的确认包即客户端的ACK报文时,删除该条目,服务器进入ESTABLISHED状态。

    未连接队列: 在服务器SYN_RCVD状态维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于 Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。

    ESTABLISHED:这个容易理解了,表示连接已经建立了。

    以上后三个状态分别对应三次握手的每次“握手”后的客户端与服务器的状态。有助于理解三次握手。

    (3)什么是四次挥手

    四次挥手,别名连接终止协议。其性质为终止协议。其实就是关闭连接的过程。

    中间过程:数据传输过程

    1)客户端发出段4,包含从序号1001开始的20个字节数据。
    2)服务器发出段5,确认序号为1021,对序号为1001-1020的数据表⽰确认收到,同时请求发 送 序号1021开始的数据,服务器在应答的同时也向客户端发送从序号8001开始的10字节数据,这称为piggyback
    3)客户端发出段6,对服务器发来的序号为8001-8010的数据表⽰确认收到,请求发送序号8011开始的数据。

      在数据传输过程中,ACK和确认序号是⾮常重要的,应⽤程序交给TCP协议发送的数据会暂存TCP层的发送缓冲区中,发出数据包给对⽅之后,只有收到对⽅应答的ACK段才知道该数据包确实发到了对⽅,可以从发送缓冲区中释放掉了,如果因为⽹络故障丢失了数据包或者丢失了对⽅发回的ACK,经过等待超时后TCP协议⾃动将发送缓冲区中的数据包重发。

    关闭连接的过程:四次挥手

    1 )客户端发出段7,FIN位表⽰关闭连接的请求。
    2)服务器发出段8,应答客户端的关闭连接请求。
    3)服务器发出段9,其中也包含FIN,向客户端发送关闭连接请求。
    4)客户端发出段10,应答服务器的关闭连接请求。

    TCP的挥手过程简言之主动关闭方被动关闭方发送不会再给你发数据了的信息被动关闭方对收到的主动关闭方的报文段进行确认;被动关闭方向主动关闭方发送我也不会再给你发数据了的信息;主动关闭方再次对被动关闭方的确认进行确认。

    (4)四次挥手中的状态

     

    FIN_WAIT_1: 这个状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,(即客户端向服务器发送FIN报文)此时该SOCKET即进入到FIN_WAIT_1状态。而当服务器端方回应ACK报文后,则客户端进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动关闭连接方)
    FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)。 TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)
    CLOSING(比较少见): 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。

    CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)
    LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)。CLOSED: 表示连接中断。

    3.为什么要三次握手和四次挥手

     (1)为什么要三次握手

    在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。

    建立连接的过程是利用客户服务器模式,假设主机A为客户端,主机B为服务器端。

    1)TCP的三次握手过程:主机A向B发送连接请求;主机B对收到的主机A的报文段进行确认;主机A再次对主机B的确认进行确认。

    2)采用三次握手是为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误。失效的连接请求报文段是指:主机A发出的连接请求没有收到主机B的确认,于是经过一段时间后,主机A又重新向主机B发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机A第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机B,主机B以为是主机A又发起的新连接,于是主机B同意连接,并向主机A发回确认,但是此时主机A根本不会理会,主机B就一直在等待主机A发送数据,导致主机B的资源浪费。

    3)采用两次握手不行,原因就是上面说的效的连接请求的特殊情况,因此采用三次握手刚刚好,两次可能出现失效,四次甚至更多次则没必要,反而复杂了。

    (2)为什么要四次挥手

    在tcp连接握手时为何ACK是和SYN一起发送,为什么这里ACK却没有和FIN一起发送呢。原因是因为tcp是全双工模式,接收到FIN时意味将没有数据再发来,但是还是可以继续发送数据。

    TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。

    (3)主动断开链接的一方为什么要进入TIME_WAIT状态

    ①为了保证服务器能够收到客户机发送的最后一个确认报文;②防止出现“已失效的连接请求报文”再次出现的情况。 

    TCP是建立在连接链路的可靠传输通信方式,若在序号10中发出ACK,由于网络原因ACK报文被对方没有收到,等到2MSL从而触发被动方重新发送FIN包,若主动方不存在TIME_WAIT 会出现如下情况:

    a. 原来的TCP信息已经不存在,主动方回复RST,引起被动方关闭流程错乱;(客户端要停留在某个状态以处理重复收到的结朿报文段(即向服务器发送确认报文段)。否则,客户端将以复位报文段来冋应服务器,服务器则认为这是一个错误, 为它期望的是一个像TCP报文段10那样的确认报文段。

    b.在原来端口上建立了新的TCP连接,影响新的流程。(当一个TCP连接处于TiME_WAit状态时,我们将无法立即使用该连接占用的端口来建立一个新连接。坚持2MSL时间的TIME_WAIT状态能够确保网络:两个传输方向上尚未被接收到的、迟到的TCP报文段都消失掉(被中转路由器丟弃)。一个新的连接可以在 2MSL时间之后安全地建立,而绝对不会接收到属于原来连接的应用程序数据 ,这就是 TIMEWAIT 状态要持续 2MSL 时间的原因。

  • 相关阅读:
    ES6-11学习笔记--深拷贝与浅拷贝
    ES6-11学习笔记--对象的扩展
    ES6-11学习笔记--箭头函数
    ES6-11学习笔记--扩展运算符与rest参数
    ES6-11学习笔记--函数的参数
    ES6-11学习笔记--数组的扩展
    ES6-11学习笔记--解构赋值
    ES6-11学习笔记--数组遍历
    班课2
    班课1
  • 原文地址:https://www.cnblogs.com/33debug/p/7060752.html
Copyright © 2011-2022 走看看