精华内容
下载资源
问答
  • 提出了一种新型的基于反馈的网络编码(FNC)重传机制,利用seen机制中的隐含信息来获取接收方解码所需的重传分组个数,并改变了编码规则使部分分组可以提前解码。该机制不仅可以处理有固定误码率的随机分组丢失,还...
  • 我们都知道 TCP 协议具有重传机制,也就是说,如果发送方认为发生了丢包现象,就重发这些数据包。很显然,我们需要一个方法来「猜测」是否发生了丢包。最简单的想法就是,接收方每收到一个包,就向发送方返回一个 ...

    上一篇介绍 TCP 的文章「TCP 三次握手,四次挥手和一些细节」反馈还不错,还是蛮开心的,这次接着讲一讲关于超时和重传那一部分。


    我们都知道 TCP 协议具有重传机制,也就是说,如果发送方认为发生了丢包现象,就重发这些数据包。很显然,我们需要一个方法来「猜测」是否发生了丢包。最简单的想法就是,接收方每收到一个包,就向发送方返回一个 ACK,表示自己已经收到了这段数据,反过来,如果发送方一段时间内没有收到 ACK,就知道很可能是数据包丢失了,紧接着就重发该数据包,直到收到 ACK 为止。

    你可能注意到我用的是「猜测」,因为即使是超时了,这个数据包也可能并没有丢,它只是绕了一条远路,来的很晚而已。毕竟 TCP 协议是位于传输层的协议,不可能明确知道数据链路层和物理层发生了什么。但这并不妨碍我们的超时重传机制,因为接收方会自动忽略重复的包。

    超时和重传的概念其实就是这么简单,但内部的细节却是很多,我们最先想到的一个问题就是,到底多长时间才能算超时呢

    超时是怎么确定的?

    一刀切的办法就是,我直接把超时时间设成一个固定值,比如说 200ms,但这样肯定是有问题的,我们的电脑和很多服务器都有交互,这些服务器位于天南海北,国内国外,延迟差异巨大,打个比方:

    • 我的个人博客搭在国内,延迟大概 30ms,也就是说正常情况下的数据包,60ms 左右就已经能收到 ACK 了,但是按照我们的方法,200ms 才能确定丢包(正常可能是 90 到 120 ms),这效率实在是有点低
    • 假设你访问某国外网站,延迟有 130 ms,这就麻烦了,正常的数据包都可能被认为是超时,导致大量数据包被重发,可以想象,重发的数据包也很容易被误判为超时。。。雪崩效应的感觉

    所以设置固定值是很不可靠的,我们要根据网络延迟,动态调整超时时间,延迟越大,超时时间越长。

    在这里先引入两个概念:

    • RTT(Round Trip Time):往返时延,也就是**数据包从发出去到收到对应 ACK 的时间。**RTT 是针对连接的,每一个连接都有各自独立的 RTT。
    • RTO(Retransmission Time Out):重传超时,也就是前面说的超时时间。

    比较标准的 RTT 定义:

    Measure the elapsed time between sending a data octet with a particular sequence number and receiving an acknowledgment that covers that sequence number (segments sent do not have to match segments received). This measured elapsed time is the Round Trip Time (RTT).

    经典方法

    最初的规范「RFC0793」采用了下面的公式来得到平滑的 RTT 估计值(称作 SRTT):

    SRTT <- α·SRTT (1 - α)·RTT

    RTT 是指最新的样本值,这种估算方法叫做「指数加权移动平均」,名字听起来比较高大上,但整个公式比较好理解,就是利用现存的 SRTT 值和最新测量到的 RTT 值取一个加权平均。

    有了 SRTT,就该设置对应的 RTO 的值了,「RFC0793」是这么算的:

    RTO = min(ubound, max(lbound, (SRTT)·β))

    这里面的 ubound 是 RTO 的上边界lbound 为 RTO 的下边界,β 称为时延离散因子,推荐值为 1.3 ~ 2.0。这个计算公式就是将 (SRTT)·β 的值作为 RTO,只不过另外限制了 RTO 的上下限

    这个计算方法,初看是没有什么问题(至少我是这么感觉的),但是实际应用起来,有两个缺陷:

    There were two known problems with the RTO calculations specified in RFC-793. First, the accurate measurement of RTTs is difficult when there are retransmissions. Second, the algorithm to compute the smoothed round-trip time is inadequate [TCP:7], because it incorrectly assumed that the variance in RTT values would be small and constant. These problems were solved by Karn’s and Jacobson’s algorithm, respectively.

    这段话摘自「RFC1122」,我来解释一下:

    • 出现数据包重传的情况下,RTT 的计算就会很“麻烦”,我画了张图来说明这些情况:

      图上列了两种情况,这两种情况下计算 RTT 的方法是不一样的(这就是所谓的重传二义性):

      • 情况一:RTT = t2 - t0
      • 情况二:RTT = t2 - t1

      但是对于客户端来说,它不知道发生了哪种情况,选错情况的结果就是 RTT 偏大/偏小,影响到 RTO 的计算。(最简单粗暴的解决方法就是忽略有重传的数据包,只计算那些没重传过的,但这样会导致其他问题。。详见 Karn’s algorithm

    • 另一个问题是,这个算法假设 RTT 波动比较小,因为这个加权平均的算法又叫低通滤波器,对突然的网络波动不敏感。如果网络时延突然增大导致实际 RTT 值远大于估计值,会导致不必要的重传,增大网络负担。( RTT 增大已经表明网络出现了过载,这些不必要的重传会进一步加重网络负担)。

    标准方法

    说实话这个标准方法比较,,,麻烦,我就直接贴公式了:

    SRTT <- (1 - α)·SRTT α·RTT //跟基本方法一样,求 SRTT 的加权平均

    rttvar <- (1 - h)·rttvar h·(|RTT - SRTT |) //计算 SRTT 与真实值的差距(称之为绝对误差|Err|),同样用到加权平均

    RTO = SRTT 4·rttvar //估算出来的新的 RTO,rttvar 的系数 4 是调参调出来的

    这个算法的整体思想就是结合平均值(就是基本方法)和平均偏差来进行估算,一波玄学调参得到不错的效果。如果想更深入了解这个算法,参考「RFC6298」。

    重传——TCP的重要事件

    基于计时器的重传

    这种机制下,每个数据包都有相应的计时器,一旦超过 RTO 而没有收到 ACK,就重发该数据包。没收到 ACK 的数据包都会存在重传缓冲区里,等到 ACK 后,就从缓冲区里删除。

    首先明确一点,对 TCP 来说,超时重传是相当重要的事件(RTO 往往大于两倍的 RTT,超时往往意味着拥塞),一旦发生这种情况,TCP 不仅会重传对应数据段,还会降低当前的数据发送速率,因为TCP 会认为当前网络发生了拥塞。

    简单的超时重传机制往往比较低效,如下面这种情况:

    假设数据包5丢失,数据包 6,7,8,9 都已经到达接收方,这个时候客户端就只能等服务器发送 ACK,注意对于包 6,7,8,9,服务器都不能发送 ACK,这是滑动窗口机制决定的,因此对于客户端来说,他完全不知道丢了几个包,可能就悲观的认为,5 后面的数据包也都丢了,就重传这 5 个数据包,这就比较浪费了。

    快速重传

    快速重传机制「RFC5681」基于接收端的反馈信息来引发重传,而非重传计时器超时。

    刚刚提到过,基于计时器的重传往往要等待很长时间,而快速重传使用了很巧妙的方法来解决这个问题:服务器如果收到乱序的包,也给客户端回复 ACK,只不过是重复的 ACK。就拿刚刚的例子来说,收到乱序的包 6,7,8,9 时,服务器全都发 ACK = 5。这样,客户端就知道 5 发生了空缺。一般来说,如果客户端连续三次收到重复的 ACK,就会重传对应包,而不需要等到计时器超时。

    但快速重传仍然没有解决第二个问题:到底该重传多少个包?

    带选择确认的重传

    改进的方法就是 SACK(Selective Acknowledgment),简单来讲就是在快速重传的基础上,返回最近收到的报文段的序列号范围,这样客户端就知道,哪些数据包已经到达服务器了。

    来几个简单的示例:

    • case 1:第一个包丢失,剩下的 7 个包都被收到了。

      当收到 7 个包的任何一个的时候,接收方会返回一个带 SACK 选项的 ACK,告知发送方自己收到了哪些乱序包。注:Left Edge,Right Edge 就是这些乱序包的左右边界

    
                 Triggering    ACK      Left Edge   Right Edge
                 Segment
    
                 5000         (lost)
                 5500         5000     5500       6000
                 6000         5000     5500       6500
                 6500         5000     5500       7000
                 7000         5000     5500       7500
                 7500         5000     5500       8000
                 8000         5000     5500       8500
                 8500         5000     5500       9000
    
    
    • case 2:第 2, 4, 6, 8 个数据包丢失。
      • 收到第一个包时,没有乱序的情况,正常回复 ACK。

      • 收到第 3, 5, 7 个包时,由于出现了乱序包,回复带 SACK 的 ACK。

      • 因为这种情况下有很多碎片段,所以相应的 Block 段也有很多组,当然,因为选项字段大小限制, Block 也有上限。

    
              Triggering  ACK    First Block   2nd Block     3rd Block
              Segment            Left   Right  Left   Right  Left   Right
                                 Edge   Edge   Edge   Edge   Edge   Edge
    
              5000       5500
              5500       (lost)
              6000       5500    6000   6500
              6500       (lost)
              7000       5500    7000   7500   6000   6500
              7500       (lost)
              8000       5500    8000   8500   7000   7500   6000   6500
              8500       (lost)
    

    不过 SACK 的规范「RFC2018」有点坑爹,接收方可能会在提供一个 SACK 告诉发送方这些信息后,又「食言」,也就是说,接收方可能把这些(乱序的)数据包删除掉,然后再通知发送方。以下摘自「RFC2018」:

    Note that the data receiver is permitted to discard data in its queue that has not been acknowledged to the data sender, even if the data has already been reported in a SACK option. Such discarding of SACKed packets is discouraged, but may be used if the receiver runs out of buffer space.

    最后一句是说,当接收方缓冲区快被耗尽时,可以采取这种措施,当然并不建议这种行为。。。

    由于这个操作,发送方在收到 SACK 以后,也不能直接清空重传缓冲区里的数据,一直到接收方发送普通的,ACK 号大于其最大序列号的值的时候才能清除。另外,重传计时器也收到影响,重传计时器应该忽略 SACK 的影响,毕竟接收方把数据删了跟丢包没啥区别。

    DSACK 扩展

    DSACK,即重复 SACK,这个机制是在 SACK 的基础上,额外携带信息,告知发送方有哪些数据包自己重复接收了。DSACK 的目的是帮助发送方判断,是否发生了包失序、ACK 丢失、包重复或伪重传。让 TCP 可以更好的做网络流控。

    关于 DSACK,「RFC2883」里举了很多例子,有兴趣的读者可以去阅读一下,我这里就不讲那么细了。


    超时和重传的内容大概就是这么多,希望对你有所帮助。

    如果本文对你有帮助,欢迎关注我的公众号 tobe的呓语 ,带你深入计算机的世界~ 公众号后台回复关键词【计算机】有惊喜哦~

    展开全文
  • rtp也有nack机制,webrtc基于rtp实现了重传在一定程度上保证可靠性。 rfc4585,看到了这么一段 : RTCP扩展反馈报文,有一种nack报文 当FMT=1并且PT=205时,代表此报文是个NACK报文 Name Value Brief ...

    RTCP扩展反馈报文 接收 触发 nack重传

    • WebRTC 中丢包重传 NACK 实现分析
    • NACK则在接收端检测到数据丢包后,发送NACK报文到发送端;
    • 发送端根据NACK报文中的序列号,在发送缓冲区找到对应的数据包,重新发送到接收端。
    • NACK需要发送端发送缓冲区的支持,RFC5104[2]定义NACK数据包的格式。

    RTCP扩展反馈报文

    • webrtc的rtp重传代码分析
    • rtp也有nack机制,webrtc基于rtp实现了重传在一定程度上保证可靠性。
    • rfc4585,看到了这么一段 : RTCP扩展反馈报文,有一种nack报文
      在这里插入图片描述

    当FMT=1并且PT=205时,代表此报文是个NACK报文

    Name Value Brief Description
    RTPFB 205 Transport layer FB message
    PSFB 206 Pyload-specific FB message

    0: unassigned
    1: Generic NACK
    2-30: unassigned
    31: reserved for future expansion of the identifier number space

    • The Generic NACK message is identified by PT=RTPFB and FMT=1.

    FCI字段会有如下图所示的数据

    在这里插入图片描述

    • PID:表示Packet ID,用于表明当前接收端丢失的数据包的序号,是接收端期待收到的下一个数据包
    • BLP:表示bitmask of following lost lost packets,占两个字节,16位,表示接着PID后面的16个数据包的丢包情况。
    • Packet identifier(PID)即为丢失RTP数据包的序列号,Bitmao of Lost Packets(BLP)指示从PID开始接下来16个RTP数据包的丢失情况。
    • rtp协议本身不会帮你重传。应用应该自己解析rtcp做处理

    解耦处理

    • REMB
    • TMMBN
    • nack列表
    • reportblock 列表
      在这里插入图片描述

    RTCPReceiver

    • IncomingPacket 收到RTCP
    void RTCPReceiver::IncomingPacket(const uint8_t* packet, size_t packet_size) {
      if (packet_size == 0) {
        RTC_LOG(LS_WARNING) << "Incoming empty RTCP packet";
        return;
      }
    
      PacketInformation packet_information;
      if (!ParseCompoundPacket(packet, packet + packet_size, &packet_information))
        return;
      TriggerCallbacksFromRtcpPacket(packet_information);
    }
    
    • 解析得到 对端的ssrc
    • 解析得到 nack 序列号:nack_sequence_numbers接收端没有收到的数据包的序号了
      在这里插入图片描述
    • 根据解析信息回调处理rtcp:TriggerCallbacksFromRtcpPacket
    // Holding no Critical section.
    void RTCPReceiver::TriggerCallbacksFromRtcpPacket(
        const PacketInformation& packet_information) {
      // Process TMMBR and REMB first to avoid multiple callbacks
      // to OnNetworkChanged.
      if (packet_information.packet_type_flags & kRtcpTmmbr) {
      //带宽估计更新
        // Might trigger a OnReceivedBandwidthEstimateUpdate.
        NotifyTmmbrUpdated();
      }
      uint32_t local_ssrc;
      std::set<uint32_t> registered_ssrcs;
      {
        // We don't want to hold this critsect when triggering the callbacks below.
        rtc::CritScope lock(&rtcp_receiver_lock_);
        local_ssrc = main_ssrc_;
        registered_ssrcs = registered_ssrcs_;
      }
      if (!receiver_only_ && (packet_information.packet_type_flags & kRtcpSrReq)) {
        //SR
        rtp_rtcp_->OnRequestSendReport();
      }
      if (!receiver_only_ && (packet_information.packet_type_flags & kRtcpNack)) {
        if (!packet_information.nack_sequence_numbers.empty()) {
          RTC_LOG(LS_VERBOSE) << "Incoming NACK length: "
                              << packet_information.nack_sequence_numbers.size();
          //nack
          rtp_rtcp_->OnReceivedNack(packet_information.nack_sequence_numbers);
        }
      }
    
      // We need feedback that we have received a report block(s) so that we
      // can generate a new packet in a conference relay scenario, one received
      // report can generate several RTCP packets, based on number relayed/mixed
      // a send report block should go out to all receivers.
      if (rtcp_intra_frame_observer_) {
        RTC_DCHECK(!receiver_only_);
        if ((packet_information.packet_type_flags & kRtcpPli) ||
            (packet_information.packet_type_flags & kRtcpFir)) {
          if (packet_information.packet_type_flags & kRtcpPli) {
            RTC_LOG(LS_VERBOSE)
                << "Incoming PLI from SSRC " << packet_information.remote_ssrc;
          } else {
            RTC_LOG(LS_VERBOSE)
                << "Incoming FIR from SSRC " << packet_information.remote_ssrc;
          }
          rtcp_intra_frame_observer_->OnReceivedIntraFrameRequest(local_ssrc);
        }
      }
      if (rtcp_loss_notification_observer_ &&
          (packet_information.packet_type_flags & kRtcpLossNotification)) {
        rtcp::LossNotification* loss_notification =
            packet_information.loss_notification.get();
        RTC_DCHECK(loss_notification);
        if (loss_notification->media_ssrc() == local_ssrc) {
          rtcp_loss_notification_observer_->OnReceivedLossNotification(
              loss_notification->media_ssrc(), loss_notification->last_decoded(),
              loss_notification->last_received(),
              loss_notification->decodability_flag());
        }
      }
      if (rtcp_bandwidth_observer_) {
        RTC_DCHECK(!receiver_only_);
        if (packet_information.packet_type_flags & kRtcpRemb) {
          RTC_LOG(LS_VERBOSE)
              << "Incoming REMB: "
              << packet_information.receiver_estimated_max_bitrate_bps;
          rtcp_bandwidth_observer_->OnReceivedEstimatedBitrate(
              packet_information.receiver_estimated_max_bitrate_bps);
        }
        if ((packet_information.packet_type_flags & kRtcpSr) ||
            (packet_information.packet_type_flags & kRtcpRr)) {
          int64_t now_ms = clock_->TimeInMilliseconds();
          rtcp_bandwidth_observer_->OnReceivedRtcpReceiverReport(
              packet_information.report_blocks, packet_information.rtt_ms, now_ms);
        }
      }
      if ((packet_information.packet_type_flags & kRtcpSr) ||
          (packet_information.packet_type_flags & kRtcpRr)) {
        rtp_rtcp_->OnReceivedRtcpReportBlocks(packet_information.report_blocks);
      }
    
      if (transport_feedback_observer_ &&
          (packet_information.packet_type_flags & kRtcpTransportFeedback)) {
        uint32_t media_source_ssrc =
            packet_information.transport_feedback->media_ssrc();
        if (media_source_ssrc == local_ssrc ||
            registered_ssrcs.find(media_source_ssrc) != registered_ssrcs.end()) {
          transport_feedback_observer_->OnTransportFeedback(
              *packet_information.transport_feedback);
        }
      }
    
      if (network_state_estimate_observer_ &&
          packet_information.network_state_estimate) {
        network_state_estimate_observer_->OnRemoteNetworkEstimate(
            *packet_information.network_state_estimate);
      }
    
      if (bitrate_allocation_observer_ &&
          packet_information.target_bitrate_allocation) {
        bitrate_allocation_observer_->OnBitrateAllocationUpdated(
            *packet_information.target_bitrate_allocation);
      }
    
      if (!receiver_only_) {
        rtc::CritScope cs(&feedbacks_lock_);
        if (stats_callback_) {
          for (const auto& report_block : packet_information.report_blocks) {
            RtcpStatistics stats;
            stats.packets_lost = report_block.packets_lost;
            stats.extended_highest_sequence_number =
                report_block.extended_highest_sequence_number;
            stats.fraction_lost = report_block.fraction_lost;
            stats.jitter = report_block.jitter;
    
            stats_callback_->StatisticsUpdated(stats, report_block.source_ssrc);
          }
        }
        if (report_block_data_observer_) {
          for (const auto& report_block_data :
               packet_information.report_block_datas) {
            report_block_data_observer_->OnReportBlockDataUpdated(
                report_block_data);
          }
        }
      }
    }
    

    处理nack

    • 重发数据包操作会先检查历史缓存中有没有数据包,如果没有,继续外层循环,重发下一个包。
    • 如果有带宽限制,需要看当前分给重发机制的带宽是否已经被用完,用完了就停止循环重发操作。
    • min_resend_time时间用于检测。如果之前有请求过重传同样序号的数据包,在短时间内是不会再重传的
      在这里插入图片描述
    • 收到对端的rtcp nack 报文后就开始发送了
    • ReSendPacket :根据seqno 做重发
    void RTPSender::OnReceivedNack(
        const std::vector<uint16_t>& nack_sequence_numbers,
        int64_t avg_rtt) {
      packet_history_->SetRtt(5 + avg_rtt);
      for (uint16_t seq_no : nack_sequence_numbers) {
        const int32_t bytes_sent = ReSendPacket(seq_no);
        if (bytes_sent < 0) {
          // Failed to send one Sequence number. Give up the rest in this nack.
          RTC_LOG(LS_WARNING) << "Failed resending RTP packet " << seq_no
                              << ", Discard rest of packets.";
          break;
        }
      }
    }
    
    • ReSendPacket
    • RTP packet history 中找到【GetPacketState】 这个序列号对应的stored_packet
    int32_t RTPSender::ReSendPacket(uint16_t packet_id) {
      // Try to find packet in RTP packet history. Also verify RTT here, so that we  验证RTT,这样不会重新发送的太频繁
      // don't retransmit too often.
      absl::optional<RtpPacketHistory::PacketState> stored_packet =
          packet_history_->GetPacketState(packet_id);
      if (!stored_packet || stored_packet->pending_transmission) {
        // Packet not found or already queued for retransmission, ignore.
        return 0;
      }
    
      const int32_t packet_size = static_cast<int32_t>(stored_packet->packet_size);
      const bool rtx = (RtxStatus() & kRtxRetransmitted) > 0;
    
    // 【GetPacketAndMarkAsPending】根据id找到RtpPacketToSend
    //第二个参数是个函数  rtc::FunctionView<std::unique_ptr<RtpPacketToSend>(const RtpPacketToSend&)>
            encapsulate)
      std::unique_ptr<RtpPacketToSend> packet =
          packet_history_->GetPacketAndMarkAsPending(
              packet_id, [&](const RtpPacketToSend& stored_packet) {
                // Check if we're overusing retransmission bitrate.
                // TODO(sprang): Add histograms for nack success or failure
                // reasons.
                std::unique_ptr<RtpPacketToSend> retransmit_packet;
                if (retransmission_rate_limiter_ &&
                    !retransmission_rate_limiter_->TryUseRate(packet_size)) {
                  return retransmit_packet;
                }
                if (rtx) {
                  retransmit_packet = BuildRtxPacket(stored_packet);
                } else {
                  retransmit_packet =
                      std::make_unique<RtpPacketToSend>(stored_packet);
                }
                if (retransmit_packet) {
                  retransmit_packet->set_retransmitted_sequence_number(
                      stored_packet.SequenceNumber());
                }
                return retransmit_packet;
              });
      if (!packet) {
        return -1;
      }
    
      //包类型是重传包
      packet->set_packet_type(RtpPacketToSend::Type::kRetransmission);
      
      //一堆RtpPacketToSend 包
      std::vector<std::unique_ptr<RtpPacketToSend>> packets;
      
      packets.emplace_back(std::move(packet));
      
      //向paced_sender_ 入队这些包
      paced_sender_->EnqueuePackets(std::move(packets));
    
      return packet_size;
    }
    
    验证RTT
    • GetPacketAndMarkAsPending 会验证包的rtt
    • 上次发送时间 + rtt 与当前时间比较,不足一个rtt 就不能再重传在这里插入图片描述

    从HISOTRY 按照id 取一个

    RtpPacketHistory::StoredPacket* RtpPacketHistory::GetStoredPacket(
        uint16_t sequence_number) {
      int index = GetPacketIndex(sequence_number);
      if (index < 0 || static_cast<size_t>(index) >= packet_history_.size() ||
          packet_history_[index].packet_ == nullptr) {
        return nullptr;
      }
      //下标取
      return &packet_history_[index];
    }
    
    • 生成index !!!! todo
    int RtpPacketHistory::GetPacketIndex(uint16_t sequence_number) const {
      if (packet_history_.empty()) {
        return 0;
      }
    
      RTC_DCHECK(packet_history_.front().packet_ != nullptr);
      int first_seq = packet_history_.front().packet_->SequenceNumber();
      if (first_seq == sequence_number) {
        return 0;
      }
    		//packet_index  默认值:请求序号- 第一个序号 
      int packet_index = sequence_number - first_seq;
      //这个
      constexpr int kSeqNumSpan = std::numeric_limits<uint16_t>::max() + 1;
    
    //sequence_number 比第一个要新?
      if (IsNewerSequenceNumber(sequence_number, first_seq)) {
        //sequence_number 比第一个序号要小,说明来晚了?
        if (sequence_number < first_seq) {
          // Forward wrap.
          packet_index += kSeqNumSpan;
        }
      } else if (sequence_number > first_seq) {
        // Backwards wrap.
        packet_index -= kSeqNumSpan;
      }
    
      return packet_index;
    }
    template <typename U>
    inline bool IsNewer(U value, U prev_value) {
      static_assert(!std::numeric_limits<U>::is_signed, "U must be unsigned");
      // kBreakpoint is the half-way mark for the type U. For instance, for a
      // uint16_t it will be 0x8000, and for a uint32_t, it will be 0x8000000.
      constexpr U kBreakpoint = (std::numeric_limits<U>::max() >> 1) + 1;
      // Distinguish between elements that are exactly kBreakpoint apart.
      // If t1>t2 and |t1-t2| = kBreakpoint: IsNewer(t1,t2)=true,
      // IsNewer(t2,t1)=false
      // rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false.
      if (value - prev_value == kBreakpoint) {
        return value > prev_value;
      }
      return value != prev_value &&
             static_cast<U>(value - prev_value) < kBreakpoint;
    }
    
    • dequeu是可以 按照index 下标访问的
      // Queue of stored packets, ordered by sequence number, with older packets in
      // the front and new packets being added to the back. Note that there may be
      // wrap-arounds so the back may have a lower sequence number.
      // Packets may also be removed out-of-order, in which case there will be
      // instances of StoredPacket with |packet_| set to nullptr. The first and last
      // entry in the queue will however always be populated.
      std::deque<StoredPacket> packet_history_ RTC_GUARDED_BY(lock_);
    

    deque

    Deque的能力

    • Deque与Vector相比,有所相同,又有所不同,下面是两者的异同点:

    • 相同之处
        1.支持随机访问,迭代器均属于random-access iterator;
        2.基于中间位置的元素的移除和插入,速度都比较慢,因为要进行大量元素的移动和复制操作;
        3.vector所支持的接口在deque上都能使用,且具有相同的效果。

    • 不同之处
        1.两端都能够进行快速的插入和移除操作;
        2.访问deque时,内部结构会多一个间接过程,因此元素的访问以及迭代器的动作会相比vector较慢;
        3.迭代器需要在不同的区块间进行跳转,因此迭代器必须是smart_pointer,不能是寻常pointer;
        4.Deque不支持对容量大小的控制,需要特别注意的是,除了首尾两端,在任何地点安插或者删除元素都会导致pointer、reference和iterator的失效;
        5.Deque重新分配内存优于vector,因为其内部结构显示,deque重新分配内存的时候,不需要复制所有的元素;
        6.Deque会释放不需要的内存块,Deque的大小是可缩减的,但是要不要这么做,如何做,取决于编译器。

    • 总结:显然,deque具有vector的特性,且比vector更强大,但C++之中,更强大的功能往往意味这更大的时空开销,如何在功能和开销上作取舍,取决于具体应用场景。

    • Deque适用场景
        1.移除和插入操作发生在首尾两端(Deque的特性决定了该操作效率惊人);
        2.无须迭代器指向其元素(Deque扩容机制导致了其迭代器更容易失效);
        3.要求不再使用的元素必须释放(Deque能够释放不使用的内存块,但C++ standard并不保证这一点,依赖于编译器实现)。

    • Deque相关操作
      构造、复制和销毁
        Deque的构造与析构函数,设计逻辑与Vector几乎相同。

    展开全文
  • HARQ重传的概述

    2017-12-12 21:18:00
    HARQ(hybrid automatic ... 重传机制有三种: =停止等待 是指协议每发送一帧数据后,等待对方的反应ack、nack 回退 : 是指不停地发送数据,无需等待对方的反馈,直到接收方反馈错误NACK,发送方就重发错误数据...

    HARQ(hybrid automatic repeatrequest 混合自动重传请求)是自动重传 ARQ和向前纠错(FEC)两种技术的集合 重传技术最多能够达到4次

       重传机制有三种:

          =停止等待   是指协议每发送一帧数据后,等待对方的反应ack、nack

            回退 : 是指不停地发送数据,无需等待对方的反馈,直到接收方反馈错误NACK,发送方就重发错误数据帧和其后的所有数据帧。

            选择重传:发送方不停地发送数据,并将发送的数据存储下来,当接收方反馈数据错误NACK,发送方就重发出错误数据帧。

    今天我们来一起了解一下LTE中运用到的一个关键技术HARQ(混合自动重传技术)

    由于信息在信道传输的过程中,会产生信息丢失,所以为了保持信息的完整性,务必需要重传信息至所有的信息都完成接收为止。

    首先,先来了解一下HARQ的相关理论:

             按照重传发生时刻,可以将HARQ分为同步和异步;

                  同步HARQ是指HARQ的传输(重传)发生在固定时刻,由于接收端预先知道传输发生的时刻,因此不需要额外的信令开销来表示HARQ进程的序号,此时的HARQ进程号可以从子帧号获得。

                   异步HARQ是指HARQ的重传可以发生在任意时刻,因为接收端不知道传输的发生时刻,所以HARQ的进程处理序号需要连同数据一起发送。

              按照重传时数据特性是否发生变化又可以将HARQ分为非自适应性和自适应性两种:

                    自适应传输:发送端根据实际的信道状态信息,改变部分的传输参数;

                    非自适应传输:传输参数相对于接收端已经知晓,因此包含传输参数的信令在非自适应传输系统中不需要再次传输。

            同步HARQ优势:1.开销小;2.在非自适应系统中接收端操作复杂度低;3.提高了信道的可靠性。

            异步HARQ优势:1.在完全自适应系统中,可以采用离散,连续的子载波分配方式,调度具有很大的灵活性;

                                             2.可以支持一个子帧多个HARQ进程;

                                             3.重传调度的灵活性。

     

    LTE下行链路系统采用异步自适应的HARQ技术,上行系统采用同步非自适应的HARQ技术;

    上行链路系统选择同步非自适应的HARQ技术,主要是因为上行链路复杂,来自其他小区干扰的不确定性,基站无法精确的估测出各个用户的信干比(SINR)值,

    接下来重点看下下行链路系统采用的异步自适应的HARQ技术,首先看一张下行HARQ时序图:

    下行传输发送端通过PDSCH来调度,PDSCH对应的ACK或者NACK在PUCCH或者PUSCH上发送;

    下行传输接收端通过PDCCH来调度;

    TX:发送端

    RX:接收端

     

    假设传输信息1234

    每经过一次传输只留下一位,比如1,2,3,4一次递进,那么久需要重传4次,才能将所有的信息完整传输完毕(理想状态)。

    每次传输,我们一般用(跟踪Chase)或者(软合并Soft Combining)来实现所有数据的合并。

    当传输大数据时,必须多增加一个递增冗余IR(IncrementalRedundancy)来降低重传时所要消耗的大量开销,即后面传的是前面没接收到的数据,已经接收到的就不再传输。

     

     

     

    OK,对HARQ就讲到这里,希望大家有什么新的见解可以跟帖留言,互相交流。下次我们继续LTE的关键技术学习。

     

    转载于:https://www.cnblogs.com/lmpsoftware/p/8029813.html

    展开全文
  • 快速重传机制基于接收端的反馈信息来引发重传,而非重传计时器的超时。 因此与超时重传相比,快速重传能更加及时有效地修复丢包情况。典型的TCP同时实现了两者 重复ACK 在详细讨论快速重传前,首先需要了解当接收到...

    一、快速重传介绍
    快速重传机制基于接收端的反馈信息来引发重传,而非重传计时器的超时。 因此与超时重传相比,快速重传能更加及时有效地修复丢包情况。典型的TCP同时实现了两者
    重复ACK
    在详细讨论快速重传前,首先需要了解当接收到失序报文段时,TCP需要立即生成确认信息(重复ACK),并且失序情况表明在后续数据到达前出现了丢段,即接收端缓存出现了空缺。发送端的工作即为尽快地、高效地填补该空缺
    当失序数据到达时,重复ACK应立即返回,不能延时发送。原因在于使发送端尽早得知有失序报文段,并告诉其空缺在哪
    当采用SACK时,重复ACK通常也包含SACK信息,利用该信息可以获知多个空缺
    二、重复ACK阈值
    网络中出现失序分组时也会产生重复ACK
    重复ACK(不论是否包含SACK信息)到达发送端表明先前发送的某个分组已丢失
    在后面“包失序与包重复”文章中我们会更详细地讨论到,重复ACK也可能在另一种情况下出现,即当网络中出现失序分组时——若接收端收到当前期盼序列号的后续分组时,当前期盼的包可能丢失,也可能仅为延迟到达
    通常我们无法得知是哪种情况,因此TCP等待一定数目的重复ACK(称为重复ACK阈值或dupthresh),来决定数据是否丢失并触发快速重传。通常,dupthresh为常量(值为3),但一些非标准化的实现方法(包括Linux)可基于当前的失序程度动态调节该值(见后面“包失序与包重复”文章)
    四、快速重传总体概述
    快速重传算法可以概括如下:
    TCP发送端在观测到至少dupthresh个重复ACK后,即重传可能丢失的数据分组,而不必等到重传计时器超时,当然也可以同时发送新的数据
    五、网络拥塞对重复ACK的影响
    根据重复ACK推断的丢包通常与网络拥塞有关,因此伴随快速重传应触发拥塞控制机制(详见后面“TCP拥塞控制”文章)
    六、SACK采用与否对重传的影响
    不采用SACK时:在接收到有效ACK前至多只能重传一个报文段
    采用SACK:ACK可包含额外信息,使得发送端在每个RTT时间内可以填补多个空缺
    下面描述一个基本快速重传算法的例子之后,我们在后面一篇文章讨论在快速重传中SACK的用法
    七、演示案例
    在下面的例子中,我们建立一个与前面文章中“RTTM对丢包和失序的鲁棒性”演示案例中类似的连接,但这次丢弃报文段23801和 26601,并且禁用SACK。我们将看到TCP怎样利用基本的快速重传算法来填补空缺。发送端为Linux 2.6系统,接收端为FreeBSD 5.4系统。下图可通过Wireshark的“统计或TCP流图或时间序列图”功能得到,该图显示了快速重传行为


    该图y轴表示相对发送序列号,x轴表示时间。黑色的Ⅰ形线段表示传输报文段的序列号范围。wireshark中的蓝色(图中的浅灰色)线段为返回的ACK号。约1.0s时刻,序列号23801发生了快速重传(初始传输不可见,因为被发送端TCP下层丢弃)。第三个重复ACK的到达触发了快速重传,图中表现为重叠的浅灰色线段。通过Wireshark的基本分析窗口也可以观察到重传过程(见下图)
    下图的第一行(40号)为ACK 23801首次到达。wireshark标示出了(红色,在下图中看来是黑色)其他“有趣的” TCP包。这些包与其他没有丢失或异常的包不同。我 们可以看到窗口更新、重复ACK和重传。0.853s时刻的窗口更新为带重复序列号的ACK(因为没有携带数据),但包含了TCP流控窗口的变动。窗口由231616字节变为233016字节。 因此,它并没有等到三个重复ACK来触发快速重传。窗口更新仅是提供了窗口通告的一个 副本。我们将在第15章中详细讨


    0.890s、 0.926s以及0.964s时刻到达的均为序列号为23801的重复ACK。第三个重复ACK的到达触发了报文段23801的快速重传,时间为0.993s。该过程也可通过Wireshark的 “统计或流图”功能来观测(见下图)


    现在我们换个角度来看0.993s时刻的快速重传过程,也可以看1.326s时刻发生的第二次快速重传,该重传是由1.322s时刻达到的ACK触发的
    第二次重传与第一次有所不同。当第一次重传发生时,发送端在执行重传前已发送的 最大序列号为(43401 + 1400 = 44801),称为恢复点。TCP在接收到序列号等于或大于恢复点的ACR时,才会被认为从重传中恢复。本例中,1.322s和1.321s时刻的ACK并不是44801,而是266010该序列号大于之前接收到的最大ACK值(23801),但 不足以到达恢复点(44801)。因此这种类型的ACK称为部分ACK。当部分ACK到达时,TCP发送端立即“发送可能丢失的报文段(这里是26601 ),并且维持这一过程直到到达或超过恢复点。如果拥塞控制机制允许(见后面的“TCP拥塞控制”),也可以同时发送新的数据
    这里的例子并没有采用SACK,不论是快速重传,还是基于“NewReno”算法 [RFC3782]恢复阶段执行的其他重传。由于没有SACK,通过观察返回的ACK号的增长情况,发送端在每个RTT内只能获知至多一个空缺
    在恢复阶段的具体行为根据TCP发送端和接收端的类型和配置差异有所不同。这里描述的是无SACK.发送端采用NewReno算法的例子,这种配置比较常见。根据NewReno算法,部分ACK只能使发送端继续处于恢复状态。对较旧的TCP版本(单纯的Reno算法)来说,没有部分ACK这个概念,任何一个可接受的ACK(序列号大于之前接收到的所有ACK)都能使发送端结束恢复阶段。这种方法可能会使TCP出现一些性能问题,我们在“TCP拥塞控制”会详细讨论。下面讨论NewReno和SACK,它们有时也被称为“高级丢失恢复”技术,以此来区别旧的方法。

    展开全文
  • 如何解决TCP重传、乱序和重复?

    千次阅读 2020-03-10 17:33:43
    TCP提供两种重传机制,一种是基于时间的超时重传,一种是基于接收端反馈消息的快速重传。相比之下前者占用更少的网络带宽,但是效率很低。而后者则相反。下面我们来具体看一下这两种机制的实现方式。 超时重传 ...
  • 关于TCP重传、乱序和重复的问题

    千次阅读 2019-03-17 11:46:28
    TCP提供两种重传机制,一种是基于时间的超时重传,一种是基于接收端反馈消息的快速重传。相比之下前者占用更少的网络带宽,但是效率很低。而后者则相反。下面我们来具体看一下这两种机制的实现方式。 超时重传 ...
  • pgm不太能用,没有想象中的可靠,重传机制貌似仍然使用组播重传,丢包率80%的网络感觉没啥改进,如果有所好转延迟估计也是个不小的问题。 后听说rtp也有nack机制,webrtc基于rtp实现了重传在一定程度上保证可靠性。 ...
  • 重传阶段根据反馈机制结果采取随机接入方式优先对有机会编码的丢失信息包进行组合,然后通过牺牲之前重传过程中传输失败的节点为信息提供空间分集增益,从而减少重传次数。最后在不同信道环境下,该策略与未协作...
  • 邻端口的调度结果并以此对目标端口所反馈的缓存信息进行修正,基于修正后的信息进行算法调度使得.FRTM-TSA 能够避免信元冲突和信元失序,也无需在输出端口设置排序缓存。理论分析和仿真结果均表明.FRTM-TSA 能够以...
  • Socket 的超时重传处理

    2020-05-08 23:43:18
    Socket 的这种机制自然有其优势所在,但是有时候我们需要保证发出的消息被准确送达。 本文思路:后端启定时器不断发送消息,直到收到前端反馈;对每一条消息用 uuid 标识,避免被前端重复响应。 一、Java 端的 ...
  • 在对长期演进(long term evolution,LTE)下行数据发送机制详细分析的基础上,针对下行数据发送过程的实现,提出一种混合自动重传请求(hybrid automatic repeat request,HARQ)选择方案,遵照协议给出的反馈时延...
  • 一个更高效的RACK机制

    千次阅读 2018-06-14 11:21:01
    tcp在kernel-4.3内核中加入了RACK机制,用从时间维度上来判断丢包,用最新被(S)ACK确认的数据包为基准,其发送时间减去一个乱序时间窗口之前的数据包如果没有收到反馈,就可以判断为丢失了。主要为了解决尾部丢包和...
  • 机会路由中的转发候选集可有效增加无线Mesh网络吞吐量和降低重传数;但是,机会路由也正遭受安全问题困扰。针对节点间的共谋攻击行为,提出一种基于反馈可信度的信任模型,并结合到机会路由中,防止共谋节点加入机会...
  • TCP定时器

    2019-04-25 21:02:33
    TCP的定时器 在TCP协议中有的时候需要定期或者按照某个算法对某个事件进行触发,那么...TCP是可靠的,因此,它对于发出去的信息,没有得到正常ACK反馈的,都会启动一个重传机制。这个重传机制使用一个重传定时器,...
  • TCP的定时器

    2017-03-21 19:53:13
    TCP的定时器 在TCP协议中有的时候需要定期或者按照某个算法对某个事件进行触发,那么这个...TCP是可靠的,因此,它对于发出去的信息,没有得到正常ACK反馈的,都会启动一个重传机制。这个重传机制使用一个重传定时
  • 在学习TCP之前我们先来看一下可靠数据传输需要提供什么样的机制: ·差错检测机制:检测数据包是否出现比特差错 ·反馈机制:接收端对接收到的数据包的反馈 ...·重传机制:解决数据包出错或丢失问题 下面就来...
  • LTE学习笔记--MAC--HARQ

    万次阅读 2018-03-16 10:21:50
    LTE中存在两种级别的重传机制:MAC层的HARQ,以及RLC层的ARQ(AM模式)。其主要作用的是MAC层的HARQ,而RLC的ARQ是作为一种补充手段而存在的。 Ps: HARQ 机制的目标在于实现非常快速的重传,其反馈出错率大概在 1%...
  • MAC--HARQ

    2019-04-11 21:38:39
    LTE中存在两种级别的重传机制:MAC层的HARQ,以及RLC层的ARQ(AM模式)。其主要作用的是MAC层的HARQ,而RLC的ARQ是作为一种补充手段而存在的。 Ps: HARQ 机制的目标在于实现非常快速的重传,其反馈出错率大概在 1%...
  • 基于重传机制的可靠数据传输协议称为自动重传请求协议(ARQ)。 1.差错检测:检验和字段shi7接收方检测到何时出现了比特差错。 2.接收方反馈:例如肯定确认和否定确认。 3.重传:接收方接收到有差错的分组时,发送方...
  • 我们对此加入了对结果图片进行检测机制,如果绘图出错会进行绘。 生成的图片支持分辨率调节 支持使用拖动等操作动态编辑绘制内容 TODO canvas2d 接口支持 base64 图片支持 测试版本 node 端服务版的 ...
  • 复习笔记 2

    2012-09-12 18:26:00
    TCP协议 具有比特差错信道上的可靠数据传输 rdt2.0 :(停等协议) 自动重传请求 ARQ 包括差错检测,接收...为了实现基于时间的重传机制,需要一个倒数计时器 允许丢包 解决停等协议中发送方信道上的利用...
  • 分析了以RTP包传输视频数据,并以RTCP包进行控制的传输机制。根据3GPP规范和相关协议,设计实现了针对AVSM码流的负载包、重传包和六种不同的RTCP包,...在立即反馈的基础上提出了一种基于分层重传的差错控制机制
  • 1.发布的消息对应一个ID(只要单个方向唯一即可,服务器端可能会根ID判断重复接收),消息重传机制确保有限次的重试,重试失败给予用户提示,发送成功会反馈确认,客户端只有收到确认信息才知道发送成功。发送消息可能...
  • 微信协议小结

    千次阅读 2014-04-03 09:02:20
    发布的消息对应一个ID(只要单个方向唯一即可,服务器端可能会根ID判断重复接收),消息重传机制确保有限次的重试,重试失败给予用户提示,发送成功会反馈确认,客户端只有收到确认信息才知道发送成功。发送消息可能...
  • TCP协议中,针对网络丢包的做法是检错重传机制。这种简单的检错重传传送协议的优点是可以保证接收机总能无差错的完成数据接收,但反馈对信道资源浪费比较严重而且当接收方和发送方距离较远时还会产生较大的时延。 ...
  • TCP/UDP

    2020-05-20 15:35:18
    没有确认应答机制,没有超时重传机制,没有连接管理机制等等(就是说没有TCP保证安全的机制)。如果因为网络故障等原因导致数据报没有传送到对端,UDP也不会给应用层反馈任何错误信息。 面向数据报 应用层给它多大的...

空空如也

空空如也

1 2 3
收藏数 53
精华内容 21
关键字:

反馈重传机制