CS144-check1

170 阅读5分钟

Reassembler 类

概述

Reassembler 类负责接收字节流中可能乱序、可能重叠且受容量限制的子字符串,并将它们按正确顺序重组。一旦从当前期望的索引开始的连续字节序列可用,它就会被写入到一个输出的 ByteStream 中。Reassembler 管理一个内部缓冲区,用于存储那些乱序到达但在输出 ByteStream 可接受容量范围内的段。

reassembler_state 结构体

这个内部结构体用于在 Reassembler 的内部缓冲区中存储各个数据段。

struct reassembler_state
{
  uint64_t first_index;   // 该数据段在原始流中的起始字节索引。
  uint64_t last_index;    // 该数据段的结束字节索引(不包含此索引对应的字节)。
  std::string data;       // 段的实际字节数据。
  bool is_last;           // 如果此段包含整个流的最后一个字节,则为 true。

  // 用于按 first_index 排序段的比较运算符。
  bool operator<( const reassembler_state& other ) const { return first_index < other.first_index; }

  // 构造函数
  reassembler_state( uint64_t first_index_in, uint64_t last_index_in, std::string data_in, bool is_last_in )
    : first_index( first_index_in )
    , last_index( last_index_in )
    , data( std::move( data_in ) )
    , is_last( is_last_in )
  {}
};

私有成员变量

  • ByteStream output_: 重组后的数据被写入的 ByteStream 对象。Reassembler 拥有这个流的所有权。
  • std::vector<reassembler_state> buffer_: 用于存储已接收但由于早期字节缺失而无法立即写入 output_reassembler_state 段的缓冲区。此向量按 first_index 保持排序。
  • uint64_t current_index_: Reassembler 期望写入到 output_ 流的下一个字节的索引。
  • uint64_t pending_size_: 当前存储在 buffer_ 中所有段的总字节数。此计数反映了待重组的唯一字节。

构造函数

explicit Reassembler( ByteStream&& output )
  : output_( std::move( output ) ), buffer_(), current_index_( 0 ), pending_size_( 0 )
{}
  • 通过移入提供的 ByteStream (output) 来初始化 Reassembler 的内部存储。
  • current_index_ (第一个期望的字节) 设置为 0。
  • pending_size_ (内部存储的字节数) 初始化为 0。
  • 内部 buffer_ 初始化为空。

主要方法

void insert( uint64_t first_index, std::string data, bool is_last_substring )

这是向 Reassembler 输入数据段的核心方法。

  • 功能:

    1. 修剪与验证:

      • 首先根据 output_ 流的 available_capacity() (可用容量) 和 current_index_ (当前应写入位置) 来验证传入的 data 段。
      • 完全落在 current_index_ 之前的数据字节将被忽略。
      • 超出 current_index_ + output_.writer().available_capacity() 范围的数据字节将被丢弃。这意味着数据会被修剪以适应当前可接受字节的窗口。
      • 如果在修剪后,数据段为空或无效 (例如,其有效起始点在其有效结束点之后),函数可能会提前返回。
    2. is_last 标志处理:

      • is_last_substring 标志与处理后的段相关联。关键在于,如果传入的段被标记为 is_last_substring,但由于容量限制而被截断 (即,其原始数据直到其原始末尾的部分并非全部都能被接受),那么其存储段的 is_last 状态实际上会被撤销。
      • 处理可能带有 is_last_substring 标志的空 data 字符串,将它们作为有效的 (可能是零长度的) 段进行处理。如果它们位于 current_index_,则可以关闭流。
    3. 缓冲与合并:

      • 为传入 data 的有效部分创建一个 reassembler_state 对象。

      • 更新 pending_size_ 以反映这个新的、有效数据段的大小。

      • 然后,代码尝试将这个新段与 buffer_ 中现有的相邻或重叠段合并:

        • 它在 buffer_ 中搜索新段可以与之合并的段 (包括在新段之后的段,以及可能在新段之前的一个段)。
        • 合并期间,数据被组合,last_index 被更新,并且 is_last 标志被传播 (如果参与合并的任一段是 is_last,则结果合并段变为 is_last)。
        • pending_size_ 会被精确调整,以移除被完全吸收的段的贡献,或解释合并段大小的变化。
        • 被完全合并到另一个段中的段将从 buffer_ 中移除。
      • 如果新段 (在任何向前合并之后) 没有被前一个段完全吸收,它将被插入到 buffer_ 中,同时保持按 first_index 的排序顺序。

    4. 写入输出流:

      • 在处理并可能插入/合并新段后,Reassembler 检查 buffer_ 开头的段 (即 buffer_.front()) 是否具有等于 current_index_first_index
      • 如果是,则这个连续的段被 pushoutput_.writer()
      • pending_size_ 因写入的数据量而减少。
      • current_index_ 因写入的数据量而前进。
      • 如果写入的段被标记为 is_last,则调用 output_.writer().close() 方法。
      • 然后从 buffer_ 中移除已写入的段。
      • (注意:当前实现仅在刚处理的段恰好位于缓冲区前端且其索引与 current_index_ 匹配时,才会尝试写入该段。它不会在单次 insert 调用中循环写入多个新形成的连续段。)

uint64_t count_bytes_pending() const

  • 功能: 返回当前存储在 Reassembler 内部 buffer_ 中、等待重组并写入输出流的唯一字节总数。
  • 返回值: pending_size_ 的值。
  • 注意: 此函数用于测试,并依赖于 insert 方法准确维护 pending_size_

注意事项总结

  1. 空的末尾段: 可能会插入""空数据,并且这个空数据可能设置了is_last_substring,所以需要当作正常数据进行处理.
  2. 末尾段的截断: 如果一个标记为 last 的段因为过长而无法完全容纳在 output_ 流的可用容量内 (考虑到 current_index_),它将被截断。我可能插入的数据过长(这个数据为last数据),但是数据长度超过了缓冲区的大小,所以此时last数据作废。