QUIC ack frame

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010643777/article/details/80889385

When you learn, teach; when you get, give.

 For some reason, I need to learn google QUIC protocol for my own good.
 But there is one thing confused me a bit, which costs me some time to figure it out. How the sender was notified some packets get lost if an acknowledgement frame lost. The ack frame works quite similar with sack in TCP. I consider one situation, a stream packet with sequence number seq1 lost and its ack get lost too. And the retransmission timer expires, the sender will send out the same packet again with a new sequence number seq2. Even if the same packet with sequence number seq2 gets the destination successfully, the seq1 is still missing. It seems the receiver keeps sending ack frame to notify seq1 is missing. Of course, this is unpractical.
 How QUIC gets out of this dilemma? There must a mechanism to notify receiver the packet seq1 is not wanting any more.
  At first, I think out one way to solve the problem. To make the ack frame delivered in reliable way, let the packet sender acknowledge the ack frame, similar the ack2 packet in UDT[1]. It makes sure that packet lost information will be guaranteed to feed back to send. And the sender can retransmit the lost packet with a new sequence number seq2, the seq1 information will not be needed at receiver side any more once the ack frame is acked.
 But QUIC is not working in this way. It uses STOP_WAITING frames to notify receiver to stop waiting some packets under specific sequence number[2].

To limit the ACK blocks to the ones that haven’t yet been received by the peer, the peer periodically sends STOP_WAITING frames that signal the receiver to stop acking packets below a specified sequence number, raising the “least unacked” packet number at the receiver. A sender of an ACK frame thus reports only those ACK blocks between the received least unacked and the reported largest observed packet numbers. It is recommended for the sender to send the most recent largest acked packet it has received in an ack as the stop waiting frame’s least unacked value.

 关于quic的ack帧,这里有个说明[3],是按照标准的一个翻译。稍微解释下:

--- src
     0                            1  => N                     N+1 => A(aka N + 3)
+---------+-------------------------------------------------+--------+--------+
|   Type  |                   Largest Acked                 |  Largest Acked  |
|   (8)   |    (8, 16, 32, or 48 bits, determined by ll)    | Delta Time (16) |
|01nullmm |                                                 |                 |
+---------+-------------------------------------------------+--------+--------+
     A             A + 1  ==>  A + N
+--------+----------------------------------------+              
| Number |             First Ack                  |
|Blocks-1|           Block Length                 |
| (opt)  |(8, 16, 32 or 48 bits, determined by mm)|
+--------+----------------------------------------+
  A + N + 1                A + N + 2  ==>  T(aka A + 2N + 1)
+------------+-------------------------------------------------+
| Gap to next|              Ack Block Length                   |
| Block (8)  |   (8, 16, 32, or 48 bits, determined by mm)     |
| (Repeats)  |       (repeats Number Ranges times)             |
+------------+-------------------------------------------------+
     T        T+1             T+2                 (Repeated Num Timestamps)
+----------+--------+---------------------+ ...  --------+------------------+  
|   Num    | Delta  |     Time Since      |     | Delta  |       Time       |
|Timestamps|Largest |    Largest Acked    |     |Largest |  Since Previous  |
|   (8)    | Acked  |      (32 bits)      |     | Acked  |Timestamp(16 bits)|
+----------+--------+---------------------+     +--------+------------------+
---
  • Ack 块段 (Ack Block Section):
    • 块个数 (Num Blocks):一个可选的8位无符号值描述了ack块的个数减一。只有在 ‘n’ 标记位为 1 时才有。
    • Ack块长度 (Ack block length):一个大小可变包号差值。对于第一个丢失包范围,ack块以最大已确认包开始。对于首个ack块,ack块的长度为 1 + 该值。对于后续的ack块,它是ack块的长度。对于非首个块,0值表示多于256个包丢失了。
    • 到下一块的间隙 (Gap to next block):一个8位的无符号值,描述了ack块之间包的个数。

 关于后面ack块的时戳信息,delta largest acked 和time since largest acked,在实际代码中根本没有体现,也就不知道协议的设计者最初的设计意图什么。
 quic的ack帧描述的接收到的数据块,以及中间的空缺的数据包,它的ack机制类似于tcp的sack。假设接收的数据包序号为1,2,3,4,6,8,9,10.那么最大的确认号(Largest Acked)就是10,第一个连续的数据块(从高到底)由8,9,10组成,这个first ack block length就是10-8+1=3,就是成功接收了三个块。这就是[3]中说的,对于首个ack块,ack块的长度为 1 + 差值。数据块6到8存在一个间隔(gap),这个gap说明的就是丢失的数据包情况,gap大小为8-6-1=1。接下来的6单独占用一个确认块,其中的值为6-6+1=1。接下来又是一个gap,值为6-4-1=1。1,2,3,4组成一个连续的块,确认块中的内容为4-1+1=4。
 而[3]中的这句话,对于非首个块,0值表示多于256个包丢失了。因为gap占用8bit,最多能够表示的空缺为255个包。假设现在有个及其变态的情况,接收的包序号为1,2,300. 300单独成一块,first ack block length就是1。而gap是300-2-1=297,这个值超出了gap能表示的范围,那么久需要使用下一个gap来表示这个情况,所以两个gap中间的 Ack Block Length设置为0,接下来的gap值是297-255=42。接下来表示1,2的确认块的值就是2了。随意最终看到的的ack的帧是这个样子的,,从first ack block length开始:

+++++++++++++++++++++++
| 1  |255| 0 | 42 | 2 |
+++++++++++++++++++++++

 这点东西,简单吧,看了一个下午,看别人的文章,很难获取具体的细节。
 Here I will analyse the acknowledge procedure of quic protocol.

//https://cs.chromium.org/chromium/src/net/third_party/quic/core/quic_connection.cc
class AckAlarmDelegate : public QuicAlarm::Delegate {
 public:
  explicit AckAlarmDelegate(QuicConnection* connection)
      : connection_(connection) {}
  AckAlarmDelegate(const AckAlarmDelegate&) = delete;
  AckAlarmDelegate& operator=(const AckAlarmDelegate&) = delete;

  void OnAlarm() override {
    DCHECK(connection_->ack_frame_updated());
    QuicConnection::ScopedPacketFlusher flusher(connection_,
                                                QuicConnection::SEND_ACK);
  }

 private:
  QuicConnection* connection_;
};
//https://cs.chromium.org/chromium/src/net/third_party/quic/core/quic_connection.cc
QuicConnection::ScopedPacketFlusher::ScopedPacketFlusher(
    QuicConnection* connection,
    AckBundling ack_mode)
    : connection_(connection),
      flush_and_set_pending_retransmission_alarm_on_delete_(false) {

        if (ShouldSendAck(ack_mode)) {
    if (!connection_->GetUpdatedAckFrame().ack_frame->packets.Empty()) {
      QUIC_DVLOG(1) << "Bundling ack with outgoing packet.";
      connection_->SendAck();
     }
}
void QuicConnection::SendAck() {
 packet_generator_.SetShouldSendAck(!no_stop_waiting_frames_);
}
//https://cs.chromium.org/chromium/src/net/third_party/quic/core/quic_packet_generator.cc
void QuicPacketGenerator::SetShouldSendAck(bool also_send_stop_waiting) {
SendQueuedFrames(/*flush=*/false);
}
void QuicPacketGenerator::SendQueuedFrames(bool flush) {
if (!AddNextPendingFrame() && first_frame){}
}
bool QuicPacketGenerator::AddNextPendingFrame() {
  if (should_send_ack_) {
    should_send_ack_ =
        !packet_creator_.AddSavedFrame(delegate_->GetUpdatedAckFrame());
    return !should_send_ack_;
  }
  //AddSavedFrame will be analysed later.
}
void QuicPacketCreator::Flush() {
  if (!HasPendingFrames() && pending_padding_bytes_ == 0) {
    return;
  }

  QUIC_CACHELINE_ALIGNED char stack_buffer[kMaxPacketSize];
  char* serialized_packet_buffer = delegate_->GetPacketBuffer();
  if (serialized_packet_buffer == nullptr) {
    serialized_packet_buffer = stack_buffer;
  }

  SerializePacket(serialized_packet_buffer, kMaxPacketSize);
  OnSerializedPacket();
}

 The logic of AddSavedFrame:

//https://cs.chromium.org/chromium/src/net/third_party/quic/core/quic_packet_creator.cc
bool QuicPacketCreator::AddPaddedSavedFrame(const QuicFrame& frame) {
  if (AddFrame(frame, /*save_retransmittable_frames=*/true)) {
    needs_full_padding_ = true;
    return true;
  }
  return false;
}
bool QuicPacketCreator::AddFrame(const QuicFrame& frame,
                                 bool save_retransmittable_frames) {
queued_frames_.push_back(frame);
}
void QuicPacketCreator::SerializePacket(char* encrypted_buffer,
                                        size_t encrypted_buffer_len) {
  size_t length = framer_->BuildDataPacket(header, queued_frames_,
                                           encrypted_buffer, packet_size_);
}

bool QuicFramer::AppendAckFrameAndTypeByte(const QuicAckFrame& frame,
                                           QuicDataWriter* writer)
{

}

 AckAlarmDelegate中函数OnAlarm()调用间隔是多少呢?25毫秒一次。

const int64_t kDefaultDelayedAckTimeMs = 25;

当然也有特例,例如第一包:

void QuicConnection::MaybeQueueAck(bool was_missing) {
        if (fast_ack_after_quiescence_ &&
            (approximate_now - time_of_previous_received_packet_) >
                sent_packet_manager_.GetRttStats()->SmoothedOrInitialRtt()) {
          // Ack the first packet out of queiscence faster, because QUIC does
          // not pace the first few packets and commonly these may be handshake
          // or TLP packets, which we'd like to acknowledge quickly.
          ack_alarm_->Set(approximate_now +
                          QuicTime::Delta::FromMilliseconds(1));
        } else {
          ack_alarm_->Set(approximate_now +
                          sent_packet_manager_.delayed_ack_time());
        }
}

[1]UDT-draft
[2]QUIC Wire Layout Specification
[3]QUIC协议规范

猜你喜欢

转载自blog.csdn.net/u010643777/article/details/80889385
ACK