CS144 Lab:Lab1

实现TCP字节流重组

该实验就是在上个实验Byte Stram基础上实现TCP字节流的重组。

在网络中,TCP建立的连接,其发送过来的数据可能丢失,乱序等,我们要根据其首个字节的序号,将这些乱序等字节流重组,使得他们恢复成有序的字节流供应用层或者网络栈进一步分析或使用。而这个实验我们就是随机收到若干个乱序的字节流,有他们的字节流(字符串形式表示)、首字节的序号以及是否是当前流的结束等信息。我们需要维护一个可靠字节流作为输出,而这个输出正是lab 0 中实现的可靠字节流Byte Stream。

StreamReassembler

在实现这个类之前,有几个问题需要明确一下。

1.容量capacity具体指的是什么?

如lab1文档所说一样,实际上我们维护的是两个容器,一个容器维护我们接收到的乱序字节流,一个容器维护我们已经排好序可以直接使用的有序字节流(实际上就是我们lab0实现的ByteStream)。这里容量指的是这两个容器容量的总和,即这两个容器存储字节总和不能超过capacity,同时这两个容器也都是动态更新的。具体如下图所示:

2.维护乱序字节流的容器具体应该接收哪些字节流?

我最初的想法是只要没超过capacity的限制,那就直接接收。

考虑例子,当capacity=8时,先来字节流“abc”,序号是0,此时他们直接就是有序的,则有序容器容量为3。此时再来字节流“ghijk”,序号是5,但此时有序我们需要的是序号为3的字节,他们并不满足条件,倘若仅考虑容量限制那我们照单全收,此时有序容器容量为3,无需容器容量为5,此时容器满了。再来字节流“def”,序号为3,但由于我们容器满了装不下,并且更糟糕的是由于容器已满并且乱序容器中存放的事靠后的字节流无法支持我们排序,所以程序就在这卡死了,再来多少字节流都只能丢弃,这个策略是糟糕的!

新的策略是考虑乱序字节流容器仅接收在能力范围内的字节流,什么意思,考虑当前乱序字节流需要的序号是index,且乱序字节流容器剩余容量为m,则只考虑字节序号为[index,index+m)之间的字节,因为超出这个范围目前我们这个容器无法处理,既然无法处理,那就直接丢弃。

3.最后具体用什么数据结构实现?

我这里采用了map<size_t,char>实现的,最初想的是每个字节一个序号会不会太浪费了,但考虑用map<size_t,string>后引发复杂的重叠问题,所以为了简单其间,还是采用了map<size_t,char>,对每个字节制定独特的序号。

最后实现代码如下:

StreamReassembler类仅添加了私有成员_buffer(map容器),_index(目前处理到哪个字节序了),_last_index(最后结束的字节序)和_eof(当前流是否结束)。

class StreamReassembler {
  private:
    // Your code here -- add private members as necessary.
    std::map<uint64_t, char> _buffer{};  //用map存储所有未排序到字节
    uint64_t _index{};      //目前处理到哪个字节了
    uint64_t _last_index{};
    ByteStream _output;  //!< The reassembled in-order byte stream
    size_t _capacity;    //!< The maximum number of bytes
    bool _eof{};           //是否已经结束了

  public:
    //! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes.
    //! \note This capacity limits both the bytes that have been reassembled,
    //! and those that have not yet been reassembled.
    StreamReassembler(const size_t capacity);

    //! \brief Receive a substring and write any newly contiguous bytes into the stream.
    //!
    //! The StreamReassembler will stay within the memory limits of the `capacity`.
    //! Bytes that would exceed the capacity are silently discarded.
    //!
    //! \param data the substring
    //! \param index indicates the index (place in sequence) of the first byte in `data`
    //! \param eof the last byte of `data` will be the last byte in the entire stream
    void push_substring(const std::string &data, const uint64_t index, const bool eof);

    //! \name Access the reassembled byte stream
    //!@{
    const ByteStream &stream_out() const { return _output; }
    ByteStream &stream_out() { return _output; }
    //!@}

    //! The number of bytes in the substrings stored but not yet reassembled
    //!
    //! \note If the byte at a particular index has been pushed more than once, it
    //! should only be counted once for the purpose of this function.
    size_t unassembled_bytes() const;

    //! \brief Is the internal state empty (other than the output stream)?
    //! \returns `true` if no substrings are waiting to be assembled
    bool empty() const;
};

类中主要函数方法实现如下:

其中,最重要的部分就是之前讨论的哪些乱序字节流缓存的问题。

void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {

    //已经收到结束的字节信号并且字节序已经写到结束信号了,就不允许继续插入了。
    if(_eof && _index == _last_index) return;
    if(eof)
    {
        _eof = eof;
        _last_index = index + data.length(); //这里记录最后的字节
    }

    //首先尽可能的将字节存入map中,首先计算能接受的窗口区间
    size_t window_l = _index;
    size_t window_r = _index + _capacity - _output.buffer_size() - 1;

    for(size_t i = 0; i < data.length(); i++) {
        if(index + i < window_l) continue;
        if(index + i > window_r) break;
        _buffer[index + i] = data[i];
    }

    //由于有新插入的,所以可以更新_buffer到有序的字节流中
    while(_buffer.find(_index) != _buffer.end()) {
        std::string st(1, _buffer[_index]);
        _buffer.erase(_index);
        _index += _output.write(st);
    }
    
    //如果收到结束信号且已经写完了,关闭ByteStream
    if(_eof && _index == _last_index) {
        _output.end_input();
    }
    
}

//返回目前还没有排序的字节数
size_t StreamReassembler::unassembled_bytes() const { 
    return _buffer.size();
}

bool StreamReassembler::empty() const { 
    return _buffer.empty(); 
}

最后贴一张成功截图,完结撒花!

### 回答1: 好的,这里是您要求的回答: 这个问题涉及到计算机系统的实验课程——深入理解计算机系统(CS:APP)的第一个lab任务,也称为Data Lab。这个任务要求学生使用C语言编写函数来实现不同的位级操作,从而深入理解计算机中数字的存储和运算。 ### 回答2: 在Data Lab的第二部分中,我们学习了如何用不超过十六个操作来实现不同的位运算。这些操作包括移位,逻辑运算,比较和掩码等。通过这些操作的实现,我们可以进一步理解底层计算机系统中位运算的实现原理。 在实现这些操作时,我们需要了解底层计算机系统的运算和数据类型。如符号扩展、零扩展和反码等。同时,在编写代码时需要熟练使用位运算的操作符号以及一些基本控制流语句如循环、条件语句等。 除了实现这些基本操作,我们也需要应用这些操作来解决一些实际问题。例如,实现一个函数,将一个十六进制数按位翻转,或是计算一个整数二进制表示中1的数量等。 通过Data Lab的学习,我们深入了解了计算机系统中底层的位运算实现原理,并学会了如何用简洁高效的代码实现这些操作。同时,这些操作也常常被用在各种领域的计算机编程中,对于未来的学习与工作都有很大的帮助。 ### 回答3: 在《深入理解计算机系统》lab1:data lab(二)中,我们主要学习了位运算和两个的补码表示。这些知识对于我们了解计算机的原理和编码方式非常重要。 在这个实验中,我们需要完成一些与位运算相关的任务。其中包括: 1. 实现位运算。我们需要用 C 语言实现一些常见的位运算,如与、或、非、异或、左移、右移等。 2. 计算 x 的相反数。 3. 检查 x 是否为零。 4. 判断 x 的符号是否为负数。 5. 计算 x 和 y 的和,但不能使用加法运算。 为了完成这些任务,我们需要对 C 语言数据类型的细节和位运算的机级实现有一定的了解。例如,我们要了解有符号整数和无符号整数的区别,以及它们在机上的表示方式。我们还需要理解位运算的计算过程,包括位移的规则、符号扩展和逻辑运算等。 通过这个实验,我们可以更深入地理解计算机的工作原理,学会用底层的方式实现高级的运算,掌握常用的位运算技巧。这对于提高编程的效率和代码的可读性都有很大帮助。同时,这也为后续的计算机科学学习打下了坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值