zoukankan      html  css  js  c++  java
  • tcp重传机制,流量控制,拥塞控制

     三次握手 

    三次握手协议的过程:

    a.客户端 向 服务器端 发送一个 SYN 包,请求一个主动打开。该包携带客户端为这个连接请求设定的随机数A作为消息列号。

    b.服务器端接收到一个SYN包后,把该包放入SYN队列中;回送一个SYN/ACK。ACK的确认码应为A+1,SYN/ACK包本身携带一个随机产生的序号B。

    c.客户端收到SYN/ACK包后,发送一个ACK的包,该包的序号被设定为A+1,而ACK的确认码为B+1。当服务器端收到这个ACK包的时候,把请求帧从SYN队列中移出,放置ACCEPT队列中。

    场景:当服务器端接收到客户端发送过来的SYN后, 回了SYN-ACK后,客户端掉线了,服务器端没有收到客户端回来的ACK,那这个连接 就 处于 一个中间状态,没成功也没失败。

    但是,服务器端如果在一定时间内没有收到TCP会重新发SYN-ACK。

    • 主机收到一个TCP包时,用两端的IP地址与端口号来标识这个TCP包属于哪个session。使用一张表来存储所有的session,表中的每条称作TCB(Transmit Control Block)。
    • TCB结构的定义包含:连接使用的源端口, 目的端口,目的ip, 序号, 应答序号, 对方窗口大小, 已方窗口大小, tcp状态, tcp输入/输出队列, 应用层输出队列, tcp的重传有关变量等。
    • 服务器端的连接数量是无限的,只受内存的限制。

     

    tcp报文头

    • 来源连接端口,16位长,识别发送连接端口
    • 目的连接端口,16位长,识别接收连接端口
    • 序列号(seq,32位长)
    • 确认号(ack,32位长),期望收到的数据的开始序列号,也即已经收到的数据的字节长度加1
    • 资料偏移(4位长),以4字节为单位计算出的数据段开始地址的偏移值。
    • 保留,需置0
    • ACK—为1表示确认号字段有效
    • SYN—为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
    • FIN—为1表示发送方没有数据要传输了,要求释放连接
    • RST—为1表示出现严重差错。可能需要重新创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求
    • 紧急指针(16位长)—本报文段中的紧急数据的最后一个字节的序号
    • 窗口(WIN,16位长)—表示从确认号开始,本报文的发送方可以接收的字节数,即接收窗口大小。用于流量控制
    • 校验和(Checksum,16位长)—对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段

     

    重传机制

    方式

    • 超时重传

      • 概念

        • 发送数据时设定一个定时器,若在指定时间内没有收到应答报文,就会重发数据
      • 发生超时重传的时机

        • 数据包丢失
        • 确认应答丢失
      • 超时时间RTO选择

        • 略大于RTT
        • 重传超时策略:超时时间间隔加倍
    • 快速重传

      • 概念

        • 发送方可以一次发送多个数据包,若中间的某个数据包丢失了,接收方会一直回复这个丢失的数据包应答报文,接收方若收到三次这个数据包的应答报文,就知道该报文还没有被接收方收到,可以重传这个数据包
      • 问题

        • 因为接收方对后续的数据包也返回了丢失的那个应答报文,所以发送方不知道后续的数据包是否丢失,也就不知道应该重传丢失的数据包还是把后续的数据包都重传
    • SACK

      • 概念

        • 选择性重传,解决快速重传不知道重传哪些报文的缺点
      • 实现方式

        • 在tcp头部“选项”字段里加一个SACK,将缓存的地图发送给发送方,这样发送方就知道哪些数据接收方收到了,哪些数据接收方没有收到,以便重传接收方没有收到的数据
      • 参数

        • 要开启SACK,需要发送方和接收方都支持:net.ipv4.tcp_sack,linux2.4以后默认打开
    • D-SACK

      • 概念

        • 对SACK的扩展,通过SACK告诉发送方哪些数据被重复接收了
      • 实现方式

        • 若有发送方有重复发送数据包,会通过SACK告诉发送方这这个数据包已被发送过
      • 好处

        • 发送方能够知道是发出去的包丢了还是接收方发送的ACK丢了

          • SACK若告知这个包已被发送过,那么说明是接收方发送的ACK丢了
        • 发送方可以知道发出去的数据包是否被网络延迟了

          • 发送方在延迟之后会重发数据包,之前的数据包若一段时间后到达了接收方,接收方返回的应答报文中能看到该数据包已经被接收了,是个重复的报文,说明被网络延迟了,而重发的数据包已被收到

    序列号与确认应答(ACK)保证了tcp的可靠传输

    滑动窗口

    引入原因

    • 每发送一个数据都需要进行确认应答,收到了再发送下一个,效率比较低。在窗口大小限制范围内,可以无需等待上一个数据包应答,就可以继续发送下一个数据包

    错误处理

    • 累计应答

      • 发送数据包丢失

        • 若发送方发送了一批数据包,中间的某个数据包丢失,那么接收方回复应答时ACK会回复丢失的那个数据包,这样接收方就知道丢失的数据包是哪一个,因此可以重新发送;
      • 应答报文丢失

        • 若接收方返回的这一组数据包中,某一个应答报文丢失了,只要最后一个应答报文发送成功了,接收方就知道这个数据包的其实是收到了的

    窗口大小

    • tcp头里的字段window

      • 接收方通过这个字段告诉接收方自己还有多少缓冲区可以接收数据,发送方就可以根据接收方的处理来发送数据,以免导致接收方处理不过来,因此窗口的大小是由接收方决定的
    • 接收方和发送方的窗口

      • 接收方和发送方的窗口大小基本相等,因为发送方的窗口大小取决于接收方,当接收方处理能力快,窗口变大,通过tcp报文中的window字段告诉接收方,若传输过程中出现了延迟,所以这时两个窗口大小不一致

    拥塞控制

    概念

    • 流量控制是避免发送方填满接收方的缓存,但若因为其他主机之间的通信造成网络拥堵,会有超时和丢包发生,这样会导致重传 ,网络负担会更大,进入恶性循环,所以tcp不能忽略网络上发生的事,当网络发生拥堵时,它会降低数据的发送量。拥塞控制的目的就是避免发送方的数据填满整个网络

    拥塞窗口cwnd

    • 发送窗口swnd和接收窗口rwnd是约等于的关系,有了拥塞窗口的概念后,发送窗口swnd=min(cwnd, rwnd)

      • 网络中没有出现拥塞,cwnd会增大,反之会减小
    • 判断网络拥塞的方法

      • 发生超时重传
    • 相关算法

      • 慢启动

        • tcp刚建立连接完成,会有个慢启动的过程,一点一点提高发送数据包的数量
        • 每接收到一个ack,cwnd大小就会加1,初始化时,cwnd大小为1,即cwnd大小按指数级增长
        • ssthreshold,slow start thresold,慢启动门限

          • 当cwnd < ssthreshold时,会采用慢启动算法
          • 当cwnd >= ssthreshold时,会使用拥塞避免算法
      • 拥塞避免

        • 每收到一个ack,cwnd增加1/cwnd。即囤积了cwnd这么多个包后,一次性发送过去。之后都会这样发送,cwnd按线性增长
        • 当一直这么增长,会慢慢进入拥塞状况,于是开始出现丢包现象,这时需要对丢失的数据进行重传,当触发了重传机制也就进入了拥塞发生算法
      • 拥塞发生

        • 超时重传

          • ssthreshold设为cwnd/2
          • cwnd设为1
          • 即一旦发生超时重传就重新进入慢启动
        • 快速重传

          • 当接收方发现丢了一个中间包时,会发送三次丢失包的ack,发送方收到后就会快速重传丢失的包
          • tcp认为这时候拥塞并不严重,只丢了一小部分包,于是

            • cwnd = cwnd/2
            • ssthreshold变为cwnd
          • 然后进入快速恢复算法
      • 快速恢复

        • cwnd = ssthresold+3
        • 重传丢失的数据包
        • 如果再收到重复的ack,那么cwnd+1
        • 收到新的ack后,cwnd变为ssthreshold,然后进入拥塞避免算法

    流量控制

    概念

    • 流量控制是基于滑动窗口实现的,tcp通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制

    问题

    • 描述

      • 接收方收到了太多数据,暂时不能接收数据了,于是返回了window大小为0
      • 发送方发现window大小为0,于是暂时不再发送数据包了
      • 接收方处理完数据,可以继续处理了,于是在之前已处理完的数据包的应答报文中更新窗口大小,但是该应答报文丢失了,导致接收方没能收到,于是就一直不发送新的数据包了,出现了死锁
    • 解决方法

      • tcp为每个连接设定一个持续计时器,只有发起连接的一方从对方收到零窗口通知,就启动计时器;如果持续计时器超时,就会发送窗口探测报文,对方会给出自己现在接收窗口的大小
      • 窗口探测次数一般为3次,每次大约30-60秒,如果三次后窗口还是0,有的tcp实现会发起RST报文来中断连接

    糊涂窗口综合症

    • 发送方

      • 发送方虽然知道窗口很大,但是每次都只发送很少的数据
    • 接收方

      • 接收方太忙,每次都只能从window中取出很少的数据,然后通知发送方,这样发送方每次也只发送很少的数据
    • 问题

      • 每次都只传输小包,效率低
    • 解决方法

      • 让接收方不通知小窗口给发送方

        • 当窗口大小小于min(mss,(缓存空间/2))时,就会向发送方通知窗口为0,阻止发送方后续发送数据过来

          • 注mss,Maximum Segment Size,最大报文长度,MSS是TCP报文段中的数据字段的最大长度,不包括TCP首部的长度
      • 让发送方不发送小包

        • Nagel算法

          • 发送条件

            • 等到窗口大小>=mss或数据大小>=mss
            • 收到之前发送数据的ack应答
          • 满足发送条件的两点才会发送
          • 设置关闭

            • Nagel算法默认开启,若想要关闭,需要在socket设置TCP_NODELAY来关闭。没有全局参数,需要每个应用根据自己的特点来关闭
        

        Silly Window Syndrome翻译成中文就是“糊涂窗口综合症”。正如你上面看到的一样,如果我们的接收方太忙了,来不及取走Receive Windows里的数据,那么,就会导致发送方越来越小。到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的window,而我们的发送方会义无反顾地发送这几个字节。

    要知道,我们的TCP+IP头有40个字节,为了几个字节,要达上这么大的开销,这太不经济了。

    另外,你需要知道网络上有个MTU,对于以太网来说,MTU是1500字节,除去TCP+IP头的40个字节,真正的数据传输可以有1460,这就是所谓的MSS(Max Segment Size)注意,TCP的RFC定义这个MSS的默认值是536,这是因为 RFC 791里说了任何一个IP设备都得最少接收576尺寸的大小(实际上来说576是拨号的网络的MTU,而576减去IP头的20个字节就是536)。

    如果你的网络包可以塞满MTU,那么你可以用满整个带宽,如果不能,那么你就会浪费带宽。(大于MTU的包有两种结局,一种是直接被丢了,另一种是会被重新分块打包发送) 你可以想像成一个MTU就相当于一个飞机的最多可以装的人,如果这飞机里满载的话,带宽最高,如果一个飞机只运一个人的话,无疑成本增加了,也而相当二。

    所以,Silly Windows Syndrome这个现像就像是你本来可以坐200人的飞机里只做了一两个人。 要解决这个问题也不难,就是避免对小的window size做出响应,直到有足够大的window size再响应,这个思路可以同时实现在sender和receiver两端。

    • 如果这个问题是由Receiver端引起的,那么就会使用 David D Clark’s 方案。在receiver端,如果收到的数据导致window size小于某个值,可以直接ack(0)回sender,这样就把window给关闭了,也阻止了sender再发数据过来,等到receiver端处理了一些数据后windows size 大于等于了MSS,或者,receiver buffer有一半为空,就可以把window打开让send 发送数据过来。
    • 如果这个问题是由Sender端引起的,那么就会使用著名的 Nagle’s algorithm。这个算法的思路也是延时处理,他有两个主要的条件:1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)收到之前发送数据的ack回包,他才会发数据,否则就是在攒数据。

    另外,Nagle算法默认是打开的,所以,对于一些需要小包场景的程序——比如像telnet或ssh这样的交互性比较强的程序,你需要关闭这个算法。你可以在Socket设置TCP_NODELAY选项来关闭这个算法(关闭Nagle算法没有全局参数,需要根据每个应用自己的特点来关闭)

    setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value,sizeof(int));

    另外,网上有些文章说TCP_CORK的socket option是也关闭Nagle算法,这不对。TCP_CORK其实是更新激进的Nagle算汉,完全禁止小包发送,而Nagle算法没有禁止小包发送,只是禁止了大量的小包发送。最好不要两个选项都设置。

    https://coolshell.cn/articles/11609.html

    http://zdyi.com/books/unp/s1/1.3.5.html

  • 相关阅读:
    JAVA学习日报 8.26
    JAVA学习日报 8.25
    JAVA学习日报 8.24
    JAVA学习日报 8.23
    Docker 详解
    DRF 3 请求响应异常处理
    DRF 2 序列化器
    DRF 1 API接口规范
    计算机计算小数的方法
    软件结构体系第二章
  • 原文地址:https://www.cnblogs.com/dream397/p/14536632.html
Copyright © 2011-2022 走看看