QUIC协议和HTTP3.0技术研究

1. 什么是QUIC

QUIC(Quick UDP Internet Connections)是Google开发的一种新的互联网传输协议。

QUIC解决了现代Web应用程序遇到的许多传输层和应用层问题,而对应用程序编写者几乎没有特殊的要求。QUIC与TCP+TLS+HTTP/2非常相似,但是它是在UDP之上实现的。因为现有协议受到遗留客户端和中间包的阻碍,所以开发者可以将QUIC作为一个自包含的协议,进行现有协议无法实现的创新。

QUIC相对于TCP+TLS+HTTP/2的优势包括:

  • 连接建立延迟低
  • 改进的拥塞控制
  • 无队头阻塞的多路复用
  • 前向纠错
  • 连接迁移

1.1 连接建立延迟低

与TCP+TLS的1-3次往返相比,QUIC握手在发送有效载荷之前通常需要零次往返。

QUIC客户端第一次连接到服务器时,客户端必须执行一次往返握手,以获取完成握手所需的信息。客户端发送早期客户端问候(CHLO),服务器发送拒绝报文(REJ),其中包含客户端继续工作所需的信息,包括源地址令牌和服务器的证书。客户端下次发送CHLO时,可以使用以前连接中的缓存凭据来立即将加密的请求发送到服务器。

1.2 拥塞控制

QUIC具有可插拔的拥塞控制,并且向拥塞控制算法提供比TCP更丰富的信息。目前,Google在QUIC的实现中使用了TCP Cubic的重新实现,并且正在尝试其他方法。

QUIC向拥塞控制算法提供的信息比TCP更丰富,比如QUIC原始和重传的每个数据包都携带一个新的序列号。这样,QUIC发送方就可以将重传的ACK与原始的ACK区别开来,并且避免了TCP的重传歧义问题。QUIC ACK还明确携带在收到数据包与发送确认之间的延迟,以及单调递增的序列号,这样可以精确地计算往返时间。

QUIC的ACK帧最多支持256个NACK范围,因此QUIC对重新排序更具弹性,并且能够在重新排序或丢包时保留更多字节。客户端和服务器都可以更准确地了解对方已收到的数据包。

1.3 复用

TCP上的HTTP2有一个比较大的问题——队头阻塞问题。应用程序将TCP连接视为字节流。当TCP数据包丢失时,该HTTP2连接上的任何数据流都无法进行转发,直到该数据包被远端重新发送和接收为止。

QUIC重新设计了多路复用操作,丢失单个流数据的数据包通常只会影响该特定流。每个流帧都可以在到达时立即分派给该流,因此无损失的流可以继续进行重组,在应用程序中继续工作。

1.4 前向纠错

为了从丢失的数据包中恢复而无需等待重传,QUIC可以用FEC数据包来补充一组数据包。与RAID-4相似,FEC数据包包含FEC组中数据包的奇偶校验。如果该组的一个数据包丢失,则可以从FEC数据包和该组中的其余数据包中恢复该数据包的内容。发送者可以决定是否发送FEC分组以优化特定场景。

1.5 连接迁移

QUIC连接由客户端随机生成的64位连接ID标识。TCP连接由4个元组的源地址、源端口、目标地址和目标端口标识。所以如果客户端更改IP地址或端口,则任何活动的TCP连接将不再有效。当QUIC客户端更改IP地址时,它可以继续使用新IP地址中的旧连接ID,而不会中断任何进行中的请求。

2. QUIC的生命周期

2.1 连接建立

QUIC的客户端指的是发起连接的那个端。QUIC连接的建立是将编码过的版本协商和握手传输合并在一起来降低连接建立的时延。接下来我们先描述版本协商。

从客户端发往服务端的每一个初始化包都必须设置版本标志为1,并且必须指明所使用的协议的版本。客户端发送的每一个包都必须设置版本标志为1,直到客户端收到服务端发送的版本标志位0的包。服务端收到第一个版本标志位0的包之后,服务端会忽略所有(有可能是延时到达的)版本标志位1的包。

当服务端收到一个包含新连接的连接ID的包时,服务端会比较客户端的版本以及服务端支持的版本。如果服务端接受了客户端的版本,在整个连接的生命周期内,服务端会使用这个协议版本。在这种情况下,所有服务端发送的包都会把版本标志设置为0。

如果客户端的版本不被服务端所接受,会引起一个1-RTT的时延。服务器会发送一个版本协商包给客户端。这个包会设置版本标志为1并且包含了服务端所支持的所有版本。

当客户端收到版本协商包,客户端会选择一个可接受的协议版本并且使用这个新版本重新发送所有的包。这些包必须设置版本标志为1并且包含新的经协商的协议版本。最终,客户端接收到第一个常规包(换言之,不是协议协商包)表示版本协商结束,之后客户端发送的所有后续包都将版本标志设置为0。

为了避免版本降级攻击,客户端在第一个包中所指定的协议版本和服务端所支持的版本集合必须被包含在加密握手数据中。客户端需要验证握手中的服务器版本列表和版本协商包中的版本列表相匹配。服务端需要验证客户端是否真的支持握手中的版本。

在连接建立的过程中,握手必须协商多种传输参数。目前已定义的传输参数会在文档的稍后部分描述。

2.2 数据传输

QUIC实现了可靠连接,拥塞控制和流量控制。QUIC的流量控制近似遵循HTTP/2的流量控制。QUIC的可靠性和拥塞控制在一个伴生文档中描述。一个QUIC连接使用一个独立的包序号空间来同时实现拥塞控制和丢包恢复。

所有在QUIC连接上传输的数据,包括加密握手数据,都是作为流内的数据被发送,除了ACK包。

2.3 QUIC流的生命周期

流是双向都拥有独立序列封装在流类型帧的数据。流可以被客户端或服务端创建。多个流可以并行交替式的发送数据,流可以被取消。QUIC流的生命周期的模型类似于HTTP/2的[RFC7540]。

流的创建是通过在指定流上发送一个流类型帧隐式完成的。为了防止流ID碰撞,服务端初始化的流的流ID必须是偶数,客户端初始化的流的流ID必须是奇数。0不是一个可用的流ID。流ID为1保留用于加密握手,应该是第一个客户端初始化的流。如果使用HTTP/2 over QUIC,ID为3保留用于传输所有其它流的压缩头部,保证头部按顺序传输和处理。

新流创建时,连接的两端的流ID都必须线性增长。比如ID为2的流可能在ID为3的流之后创建,但是ID为7的流必须不能在ID为9的流之后创建。对端可能收到乱序的流。比如,服务端先收到包序号为10从属于流ID为9的帧,再收到包序号为9从属于流ID为7的帧,服务器应该优雅处理这种情况。

如果一端收到了一条流的帧但是不想接收这条流,那么在这端可以立即回复一个RST_STREAM帧(下面有描述)。注意,初始化的那端可能已经在这条流上发数据了,接收端应该忽略这些数据。

如果一条流被创建了,它可以用来发送和接收数据。这意味着一系列的流类型帧数据可以被QUIC端在流上发送直到该方向上的流被关闭。

QUIC的任意一端都可以正常的关闭流。流被关闭有三种方式:

  1. 正常关闭
    由于流是双工的,所以流可以半关闭或者全关闭。当流的一端发送了一个FIN标志为1的帧,流在这个方向进入半关闭状态。FIN表示流上发送FIN的一方没有更多数据要发送了。如果QUIC端既发送了FIN又接收了FIN,那么这个流被认为全关闭状态。FIN应该在发送完流上的用户数据后发送,FIN标志可以用一个空帧发送。
  2. 强制关闭
    客户端和服务端都可以在任意时刻发送RST_STREAM帧。一个RST_STREAM帧包含了一个标识失败原因的错误码(错误码列表在文档后续部分展示)。如果是流发起者发送RST_STREAM帧,表示流发生了失败并且没有更多数据在流上发送。当流接收者发送RST_STREAM,那么发送端应该停止在这条流上发送任何数据。流接收端应该注意这里有一个竞态条件,在发送端已经发送了的数据和RST_STREAM帧被接收到的时间之间。为了确保连接级别的流量控制被正确统计,即使发送端已经收到了RST_STREAM,发送端还必须确保流上的FIN和所有数据被对端接收了或者RST_STREAM已经被对端发送了。这也意味着RST_STREAM的发送者需要对这条流上的流类型帧保持回复(根据WINDOW_UPDATE)来确保发送端不会被流量控制所阻塞而触发发送FIN。
  3. 连接关闭时,流也会被关闭

2.4 连接关闭

连接应该保持打开直到它们的空闲时间达到了之前协商的时间。当服务端决定关闭一个空闲连接,服务端不应该通知客户端以避免唤醒手机设备。一个QUIC连接一旦被建立了,可以被两种方式关闭:

  1. 显式关闭
    一端发送一个CONNECTION_CLOSE帧给对端标识一个连接的关闭。在发送CONNECTION_CLOSE之前可能发送GOAWAY帧来标识这个连接很快会被关闭。发送一个GOAWAY帧用于通知对端目前所有活跃的流会继续工作,但是GOAWAY的发送者不会再初始化任何其他的流并且不会再接收新的流。在活跃的流关闭时,可能会发送CONNECTION_CLOSE。如果一端发送了CONNECTION_CLOSE而还有未关闭的流是活跃的(一个或多个流上没有FIN标志也没有RST_STREAM帧被发送或接收),那么对端必须假设这种流是不完整的并且被异常关闭了。
  2. 隐式关闭
    QUIC连接的默认超时关闭时间是30秒,并且是在连接协商阶段的一个必须的参数("ICSL")。最大值是10分钟。如果在超时时间内没有网络活动,连接会被关闭。默认会发送一个CONNECTION_CLOSE帧。在发送显式关闭代价太大的情况下(比如说移动网络)可以开启静音关闭选项。

3. 编译

一组示例server和client程序已经被包含在chromium源码中,所以运行之前要拉取chromium源码。

3.1 获取depot_tools工具

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

depot_tools目录加进环境变量(注意不要用~代替home目录,否则后面步骤会运行出错)

3.2 获取chromium源码

使用fetch --nohooks chromium命令获取源码,可以加参数--no-history加快速度(不获取历史版本记录)。

3.3 安装build所需的依赖

进入src文件夹,执行脚本文件install-build-deps.sh

之后执行gclient runhooks运行Chromium特定的hooks,它将下载其他二进制文件和其他可能需要的东西。

3.4 构建

Chromium源码中包含了一组QUIC示例服务端与客户端,创建构建目录并使用ninja -C out/Debug quic_server quic_client构建。

4. 相关配置

下载www.example.org的副本,quic_server将在本地提供该副本。

获取到的index.html需要修改,按官网要求修改其header。

  • 移除(如果存在):"Transfer-Encoding: chunked"
  • 移除(如果存在):"Alternate-Protocol: ..."
  • 添加:X-Original-Url: https://www.example.org/

运行服务器需要有效的证书和pkcs8格式的私钥。使用以下脚本来生成。

cd net/tools/quic/certs
./generate-certs.sh
cd -

除了服务器的证书和公共密钥,此脚本还将生成一个CA证书(net/tools/quic/certs/out/2048-sha256-root.pem),需要将其添加到OS的根证书存储中以便在证书验证期间将其信任。

安装libnss3-tools工具

sudo apt-get install libnss3-tools

添加证书。

certutil -d sql:$HOME/.pki/nssdb/ -A -t "C,," -n 证书别名 -i 证书路径

但从后面运行时的报错可以得知,这一步没有生效,暂时没有解决这个问题。这里是官方导入证书的教程。

https://chromium.googlesource.com/chromium/src/+/master/docs/linux/cert_management.md

5. 运行测试

运行服务端程序,注意要指定端口。、

./out/Debug/quic_server \
  --quic_response_cache_dir=/tmp/quic-data/www.example.org \
  --certificate_file=net/tools/quic/certs/out/leaf_cert.pem \
  --key_file=net/tools/quic/certs/out/leaf_cert.pkcs8 \
  --host=127.0.0.1 \
  --port=6121

新开终端运行客户端程序,因为我证书没有设置好,直接运行会报错,所以运行时要加上flag --disable_certificate_verification

./out/Debug/quic_client --host=127.0.0.1 --port=6121 https://www.example.org/ --disable_certificate_verification

正确获取了之前下载的www.example.org

6. 部分源码分析

这里使用libquic代码进行分析。

6.1 数据包发送过程

QuicPacketCreator::SerializePacket这个函数对queued_frames_中的数据进行了序列化。而一次的封包操作,数据包的长度为max_plaintext_size_,具体大小不太清楚,总之,不超过kMaxPacketSize(1452)。

void QuicPacketCreator::SerializePacket(char* encrypted_buffer,
                                        size_t encrypted_buffer_len)
{
  size_t length = framer_->BuildDataPacket(header, queued_frames_,
                                           encrypted_buffer, packet_size_);
} 

疑惑的是,这里的queued_frames_中的所有帧的长度不超过max_plaintext_size_的大小的?发送的数据,要是超过了这个长度,多余的数据该存放在哪里呢?数据被缓存之后,什么时候会被再次发送出去呢?

从数据的发送开始,也就是ReliableQuicStream::WriteOrBufferData函数分析:

void ReliableQuicStream::WriteOrBufferData(
    StringPiece data,
    bool fin,
    QuicAckListenerInterface* ack_listener) {
      if (queued_data_.empty()) {
    struct iovec iov(MakeIovec(data));
    consumed_data = WritevData(&iov, 1, fin, ack_listener);
    DCHECK_LE(consumed_data.bytes_consumed, data.length());
  }

  // If there's unconsumed data or an unconsumed fin, queue it.
  //没有写完,则将剩余的数据缓寸,因为数据包的长度超出了kMaxPacketSize大小
  if (consumed_data.bytes_consumed < data.length() ||
      (fin && !consumed_data.fin_consumed)) {
    StringPiece remainder(data.substr(consumed_data.bytes_consumed));
    queued_data_bytes_ += remainder.size();
    queued_data_.emplace_back(remainder.as_string(), ack_listener);
  }
  }
  //当sent_packet_manager允许发送的时候,就是拥塞控制允许向外发送,缓存的数据被发送出去。
  void ReliableQuicStream::OnCanWrite(){
   while (!queued_data_.empty()) {
    PendingData* pending_data = &queued_data_.front();
    QuicAckListenerInterface* ack_listener = pending_data->ack_listener.get();
    if (queued_data_.size() == 1 && fin_buffered_) {
      fin = true;
    }
    if (pending_data->offset > 0 &&
        pending_data->offset >= pending_data->data.size()) {
      // This should be impossible because offset tracks the amount of
      // pending_data written thus far.
      QUIC_BUG << "Pending offset is beyond available data. offset: "
               << pending_data->offset << " vs: " << pending_data->data.size();
      return;
    }
    size_t remaining_len = pending_data->data.size() - pending_data->offset;
    struct iovec iov = {
        const_cast<char*>(pending_data->data.data()) + pending_data->offset,
        remaining_len};
    QuicConsumedData consumed_data = WritevData(&iov, 1, fin, ack_listener);
    queued_data_bytes_ -= consumed_data.bytes_consumed;
    if (consumed_data.bytes_consumed == remaining_len &&
        fin == consumed_data.fin_consumed) {
      queued_data_.pop_front();
    } else {
      if (consumed_data.bytes_consumed > 0) {
        pending_data->offset += consumed_data.bytes_consumed;
      }
      break;
    }
  } 
  }
  QuicConsumedData ReliableQuicStream::WritevData(
    const struct iovec* iov,
    int iov_count,
    bool fin,
    QuicAckListenerInterface* ack_listener) {
      QuicConsumedData consumed_data =
      WritevDataInner(QuicIOVector(iov, iov_count, write_length),
                      stream_bytes_written_, fin, ack_listener);
   }
   QuicConsumedData ReliableQuicStream::WritevDataInner(
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* ack_notifier_delegate) {
  return session()->WritevData(this, id(), iov, offset, fin,
                               ack_notifier_delegate);
}
QuicConsumedData QuicSession::WritevData(
    ReliableQuicStream* stream,
    QuicStreamId id,
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* ack_notifier_delegate) {
      QuicConsumedData data =
      connection_->SendStreamData(id, iov, offset, fin, ack_notifier_delegate);
    }
    QuicConsumedData QuicConnection::SendStreamData(
    QuicStreamId id,
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* listener){
      return packet_generator_.ConsumeData(id, iov, offset, fin, listener);
    }

上面显示的两个函数均有调用consumed_data = WritevData(&iov, 1, fin, ack_listener)。能保证写进queued_frames_中的所有帧长不会超过一个向网络中发送一个UDP数据包的长度的操作就是 packet_generator_.ConsumeData

QuicConsumedData QuicPacketGenerator::ConsumeData(
    QuicStreamId id,
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* listener){
    //当拥塞控制不允许写的时候,或者数据包全部被Consume,才会退出
  while (delegate_->ShouldGeneratePacket(
      HAS_RETRANSMITTABLE_DATA, has_handshake ? IS_HANDSHAKE : NOT_HANDSHAKE)) {
    QuicFrame frame;
    if (!packet_creator_.ConsumeData(id, iov, total_bytes_consumed,
                                     offset + total_bytes_consumed, fin,
                                     has_handshake, &frame)) {
      // The creator is always flushed if there's not enough room for a new
      // stream frame before ConsumeData, so ConsumeData should always succeed.
      //因为下面调用了packet_creator_.Flush();,ConsumeData每次调用都会返回true
      QUIC_BUG << "Failed to ConsumeData, stream:" << id;
      return QuicConsumedData(0, false);
    }

    // A stream frame is created and added.
    size_t bytes_consumed = frame.stream_frame->data_length;
    if (listener != nullptr) {
      packet_creator_.AddAckListener(listener, bytes_consumed);
    }
    total_bytes_consumed += bytes_consumed;
    fin_consumed = fin && total_bytes_consumed == iov.total_length;
    DCHECK(total_bytes_consumed == iov.total_length ||
           (bytes_consumed > 0 && packet_creator_.HasPendingFrames()));

    if (!InBatchMode()) {
      packet_creator_.Flush();
    }

    if (total_bytes_consumed == iov.total_length) {
      // We're done writing the data. Exit the loop.
      // We don't make this a precondition because we could have 0 bytes of data
      // if we're simply writing a fin.
      break;
    }
    // TODO(ianswett): Move to having the creator flush itself when it's full.
    packet_creator_.Flush();
  }

  // Don't allow the handshake to be bundled with other retransmittable frames.
  if (has_handshake) {
    SendQueuedFrames(/*flush=*/true);
  }

  DCHECK(InBatchMode() || !packet_creator_.HasPendingFrames());
  return QuicConsumedData(total_bytes_consumed, fin_consumed);
   }
   bool QuicPacketCreator::ConsumeData(QuicStreamId id,
                                    QuicIOVector iov,
                                    size_t iov_offset,
                                    QuicStreamOffset offset,
                                    bool fin,
                                    bool needs_full_padding,
                                    QuicFrame* frame)
{
  CreateStreamFrame(id, iov, iov_offset, offset, fin, frame);
  if (!AddFrame(*frame, /*save_retransmittable_frames=*/true)) {
    // Fails if we try to write unencrypted stream data.
    delete frame->stream_frame;
    return false;
  }
}                         

Flush将数据发送到网络中:

void QuicPacketCreator::Flush() {
  if (!HasPendingFrames()) {
    return;
  }

  // TODO(rtenneti): Change the default 64 alignas value (used the default
  // value from CACHELINE_SIZE).
  ALIGNAS(64) char seralized_packet_buffer[kMaxPacketSize];
  SerializePacket(seralized_packet_buffer, kMaxPacketSize);
  OnSerializedPacket();
}
void QuicConnection::OnSerializedPacket(SerializedPacket* serialized_packet) {
SendOrQueuePacket(serialized_packet);
}
void QuicConnection::SendOrQueuePacket(SerializedPacket* packet) {
  if (!queued_packets_.empty() || !WritePacket(packet)) {
    // Take ownership of the underlying encrypted packet.
    packet->encrypted_buffer = QuicUtils::CopyBuffer(*packet);
    queued_packets_.push_back(*packet);
    packet->retransmittable_frames.clear();
  }
}
bool QuicConnection::WritePacket(SerializedPacket* packet) {
  WriteResult result = writer_->WritePacket(
      packet->encrypted_buffer, encrypted_length, self_address().address(),
      peer_address(), per_packet_options_);
      //拥塞控制相关。
  bool reset_retransmission_alarm = sent_packet_manager_->OnPacketSent(
      packet, packet->original_path_id, packet->original_packet_number,
      packet_send_time, packet->transmission_type, IsRetransmittable(*packet));      
}

6.2 帧重传部分

QUIC代码里中的重传逻辑,实现了两种处理模式,一个是在connection层实现的重传,另一个是在session层实现的重传。在应用中只能启用一个,要么有由connection负责重传,要么由session负责重传。

QuicFrame定义:

struct QUIC_EXPORT_PRIVATE QuicFrame {
  explicit QuicFrame(QuicStreamFrame stream_frame);
      struct {
      QuicFrameType type;

      // TODO(wub): These frames can also be inlined without increasing the size
      // of QuicFrame: QuicStopWaitingFrame, QuicRstStreamFrame,
      // QuicWindowUpdateFrame, QuicBlockedFrame, QuicPathResponseFrame,
      // QuicPathChallengeFrame and QuicStopSendingFrame.
      union {
        QuicAckFrame* ack_frame;
        QuicStopWaitingFrame* stop_waiting_frame;
        QuicRstStreamFrame* rst_stream_frame;
        QuicConnectionCloseFrame* connection_close_frame;
        QuicGoAwayFrame* goaway_frame;
        QuicWindowUpdateFrame* window_update_frame;
        QuicBlockedFrame* blocked_frame;
        QuicApplicationCloseFrame* application_close_frame;
        QuicNewConnectionIdFrame* new_connection_id_frame;
        QuicRetireConnectionIdFrame* retire_connection_id_frame;
        QuicPathResponseFrame* path_response_frame;
        QuicPathChallengeFrame* path_challenge_frame;
        QuicStopSendingFrame* stop_sending_frame;
        QuicMessageFrame* message_frame;
        QuicCryptoFrame* crypto_frame;
        QuicNewTokenFrame* new_token_frame;
      };
    };
}

数据重传首先要判断数据的丢包:

bool QuicSentPacketManager::OnAckFrameEnd(QuicTime ack_receive_time){
  PostProcessAfterMarkingPacketHandled(last_ack_frame_, ack_receive_time,
                                       rtt_updated_, prior_bytes_in_flight);
}
void QuicSentPacketManager::PostProcessAfterMarkingPacketHandled(
    const QuicAckFrame& ack_frame,
    QuicTime ack_receive_time,
    bool rtt_updated,
    QuicByteCount prior_bytes_in_flight){
     InvokeLossDetection(ack_receive_time);
    }
void QuicSentPacketManager::InvokeLossDetection(QuicTime time) {
	MarkForRetransmission(packet.packet_number, LOSS_RETRANSMISSION);    
 }
 //在这里就进入分叉处理,
void QuicSentPacketManager::MarkForRetransmission(
    QuicPacketNumber packet_number,
    TransmissionType transmission_type) {
  //  记录要重传的数据包序号,后续connection层的重传会用到
  if (!session_decides_what_to_write()) {
    if (!unacked_packets_.HasRetransmittableFrames(*transmission_info)) {
      return;
    }
    if (!QuicContainsKey(pending_retransmissions_, packet_number)) {
      pending_retransmissions_[packet_number] = transmission_type;
    }
    return;
  }
//如果session_decides_what_to_write_开启,则由session负责重传。
  HandleRetransmission(transmission_type, transmission_info);    
    }

6.2.1 connection负责的重传

void QuicConnection::WritePendingRetransmissions()
{
  DCHECK(!session_decides_what_to_write());
  // Keep writing as long as there's a pending retransmission which can be
  // written.
  while (sent_packet_manager_.HasPendingRetransmissions() &&
         CanWrite(HAS_RETRANSMITTABLE_DATA)) {
    const QuicPendingRetransmission pending =
        sent_packet_manager_.NextPendingRetransmission();

    // Re-packetize the frames with a new packet number for retransmission.
    // Retransmitted packets use the same packet number length as the
    // original.
    // Flush the packet generator before making a new packet.
    // TODO(ianswett): Implement ReserializeAllFrames as a separate path that
    // does not require the creator to be flushed.
    // TODO(fayang): FlushAllQueuedFrames should only be called once, and should
    // be moved outside of the loop. Also, CanWrite is not checked after the
    // generator is flushed.
    {
      ScopedPacketFlusher flusher(this, NO_ACK);
      packet_generator_.FlushAllQueuedFrames();
    }
    DCHECK(!packet_generator_.HasQueuedFrames());
    char buffer[kMaxPacketSize];
    packet_generator_.ReserializeAllFrames(pending, buffer, kMaxPacketSize);
  }
}
void QuicPacketCreator::ReserializeAllFrames(
    const QuicPendingRetransmission& retransmission,
    char* buffer,
    size_t buffer_len) {
   SerializePacket(buffer, buffer_len);
  packet_.original_packet_number = retransmission.packet_number;   
   }

这里最终还是去stream中的send_buffer_获取数据。packet_.original_packet_number记录数据包上次发送使用的序列号,下次发送的时候回调用QuicSentPacketManager::OnPacketSent将原来记录的丢失帧信息更新。

bool QuicSentPacketManager::OnPacketSent(
    SerializedPacket* serialized_packet,
    QuicPacketNumber original_packet_number,
    QuicTime sent_time,
    TransmissionType transmission_type,
    HasRetransmittableData has_retransmittable_data) {
    unacked_packets_.AddSentPacket(serialized_packet, original_packet_number,
                                 transmission_type, sent_time, in_flight);  
    }
    void QuicUnackedPacketMap::AddSentPacket(SerializedPacket* packet,
                                         QuicPacketNumber old_packet_number,
                                         TransmissionType transmission_type,
                                         QuicTime sent_time,
                                         bool set_in_flight){
  if (old_packet_number.IsInitialized()) {
    TransferRetransmissionInfo(old_packet_number, packet_number,
                               transmission_type, &info);
  }  
}

6.2.2 session负责的重传

在session层实现的重传,就不需要sent_packet_manager_.NextPendingRetransmission()获取pending中含有的可重传帧的原理的传输序号了(retransmission.packet_number)。也可以说QuicUnackedPacketMap中的记录的unacked_packets_信息就不太重要了。

void QuicSentPacketManager::HandleRetransmission(
    TransmissionType transmission_type,
    QuicTransmissionInfo* transmission_info) {
   unacked_packets_.NotifyFramesLost(*transmission_info, transmission_type); 
    }
    
void QuicUnackedPacketMap::NotifyFramesLost(const QuicTransmissionInfo& info,
                                            TransmissionType type) {
  DCHECK(session_decides_what_to_write_);
  for (const QuicFrame& frame : info.retransmittable_frames) {
    session_notifier_->OnFrameLost(frame);
  }
}

void QuicSession::OnFrameLost(const QuicFrame& frame){
  QuicStream* stream = GetStream(frame.stream_frame.stream_id);
  if (stream == nullptr) {
    return;
  }
  stream->OnStreamFrameLost(frame.stream_frame.offset,
                            frame.stream_frame.data_length,
                            frame.stream_frame.fin);
}
void QuicStream::OnStreamFrameLost(QuicStreamOffset offset,
                                   QuicByteCount data_length,
                                   bool fin_lost) {
  if (data_length > 0) {
    send_buffer_.OnStreamDataLost(offset, data_length);
  }
}
void QuicStreamSendBuffer::OnStreamDataLost(QuicStreamOffset offset,
                                            QuicByteCount data_length) {
  for (const auto& lost : bytes_lost) {
    pending_retransmissions_.Add(lost.min(), lost.max());
  }
}

再次发送数据的逻辑:

void QuicStream::OnCanWrite() {
  if (HasPendingRetransmission()) {
    WritePendingRetransmission();
    // Exit early to allow other streams to write pending retransmissions if
    // any.
    return;
  }
}
void QuicStream::WritePendingRetransmission() {
      consumed = session()->WritevData(this, id_, pending.length, pending.offset,
                                can_bundle_fin ? FIN : NO_FIN);
}

参考资料

posted @ 2021-01-28 23:18  张旭SA20225601  阅读(862)  评论(0编辑  收藏  举报