Yra's blog Yra's blog
Homepage
  • LeetCode周赛
  • Acwing周赛
  • 刷题整理
  • 题解
  • CPP
  • Golang
  • System

    • MIT6.S081 Labs
  • Computer Networking

    • CS144: Computer Networking
  • DataBase

    • MySQL
    • Redis
  • Others

    • ......
  • Paper Reading

    • Petri Net
  • Static Analysis

    • NJU Course Notes
  • Deep Learning

    • Dive into Deep Learning
Casual Records
Archives

Yra

Only a vegetable dog.
Homepage
  • LeetCode周赛
  • Acwing周赛
  • 刷题整理
  • 题解
  • CPP
  • Golang
  • System

    • MIT6.S081 Labs
  • Computer Networking

    • CS144: Computer Networking
  • DataBase

    • MySQL
    • Redis
  • Others

    • ......
  • Paper Reading

    • Petri Net
  • Static Analysis

    • NJU Course Notes
  • Deep Learning

    • Dive into Deep Learning
Casual Records
Archives
  • System

  • Computer Networking

    • CS144:Computer Networking

      • Lab0:Networking warmup
      • Lab1:stitching substrings into a byte stream
        • Lab2:The TCP Receiver
        • Lab3:The TCP Sender
        • Lab4:The TCP Connection
    • DataBase

    • Software Engineering

    • Others

    • Learning Notes
    • Computer Networking
    • CS144:Computer Networking
    Yra
    2023-03-13
    目录

    Lab1:Stitching substrings into a byte stream

    # Lab1:Stitching substrings into a byte stream

    lab1.pdf (cs144.github.io) (opens new window)

    在这个 lab 中,我们要实现一个 StreamReassembler。也就是说,在 lab0 实现的 ByteStream 基础上,由于发送的分组可能是失序的,因此我们要将分组重排,以达到 TCP 的按序传送特点。另外,需要注意的是,分组还可能会发生重复等情况。

    在 StreamReassembler 中有一个 ByteStream,其用来存放已经顺序的数据,由于 TCP 是累计确认的,因此我们要维护一个从 0 开始的变量 expected_num(与自顶向下书上相同)表示下一个要接受的数据。如果中间有数据失序,也就是 expected_num 的值代表的字节没有被接受到,那么我们就暂停存储数据到 ByteStream,转而将失序的数据缓存,直到前面暂未收到的数据补上了才进一步存储进 ByteStream。上面是大概的思路,接下来我们开始具体实现。

    首先,我们先需要明确手册中的 capacity 是什么(一开始理解错了导致写了好久错误代码...)

    image-20230313230530670

    这是一张官方给的图,可以发现我们的 capacity 分配了一部分空间给了 ByteStream,用来存储按序但还未被读取的数据。剩余部分给了 Reassembler 中的辅助数据结构。具体的,假设此时 Reassembler 可用的空间还有 bytes,则我们可以存储的数据范围就是 ,而超出这部分的字节都将被抛弃。

    接着,在 Reassembler 中,我们用 unordered_map,也就是哈希表来存储数据(感觉可以多耗费点空间,用双端队列等 DS 来存储,效率会更高)。

    最后,代码中还要求我们实现 EOF,也就是说当我们存储到了 EOF 表示的字节后就要调用 lab0 中实现的 end_input() 来关闭写入。我们用两个变量 end_flag、end_num 来实现这一功能。

    下面先给出需要定义的 private 成员,作用看注释即可。

      private:
        // Your code here -- add private members as necessary.
    
        ByteStream _output;  //!< The reassembled in-order byte stream
        size_t _capacity;    //!< The maximum number of bytes
    
        // std::set<std::pair<int, std::string>> buf; 
        std::unordered_map<size_t, char> buf; // Reassembler 存储字节的数据结构
        size_t expected_num; // 下一个期望得到的字节序号
        bool end_flag; // 是否要求以 EOF 来结束
        size_t end_num; // 最后一个数字 + 1 的位置
    
    

    来到代码实现部分,我们先实现两个比较简单的函数。

    未被重排的字节个数就是 buf 的大小,而是否为空直接判断 buf 即可。

    size_t StreamReassembler::unassembled_bytes() const {
        return buf.size();
    }
    
    bool StreamReassembler::empty() const {
        return buf.empty();
    }
    

    最后是最主要的 push_substring 函数:

    开始的时候我们先计算可以在 Reassembler 中存储的最大字节序号 up,那么如过此时 end_flag 已被标记,那么我们的 up 还不能超过 end_num

    size_t up = expected_num + _capacity - stream_out().buffer_size();
    if (end_flag) {
        up = std::min(up, end_num);
    }
    if (index >= up) return; // 不符合直接 return
    

    接着我们从第一个有效的,即大于 expected_num 的下标开始存储数据

    for (size_t i = expected_num > index ? expected_num - index : 0; i < data.size() && index + i < up; i++) {
    	// if (index + i < expected_num) continue;
    	buf[index + i] = data[i];
    }   
    

    那么如果 index 是小于 expected_num 的,那么代表这是一个重复传输的字节,因此我们的起始位置直接从 expected_num - index 开始即可;index + i 表示当前字节的下标。

    每次存储完数据之后,我们要看看此时能从 expected_num 开始有序地读入多少个字节,每读完一个还要将其删去,将可读入的字节存放在 s 里,最后将 s 写入 ByteStream。

    std::string s;
    for (size_t i = expected_num; i < up; i++) {
    	if (buf.count(i) == 0) break;
        s += buf[i];
        expected_num += 1;
        buf.erase(i);
    }
    stream_out().write(s);
    

    最后,我们需要对 eof 为 1 的情况特殊处理,打上标记和更新 end_num。另外满足条件的话还要结束输入。

    if (eof) {
    	end_flag = true;
    	end_num = index + data.size();
    }
    
    if (end_flag && expected_num == end_num) {
    	stream_out().end_input();
    }
    

    完整代码:

    void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
        size_t up = expected_num + _capacity - stream_out().buffer_size(); // Reassembler 中能存储的最大字节序号 up
        if (end_flag) {
            up = std::min(up, end_num);
        }
        if (index >= up) return; // 不符合直接 return
        
        for (size_t i = expected_num > index ? expected_num - index : 0; i < data.size() && index + i < up; i++) {
            // if (index + i < expected_num) continue;
            buf[index + i] = data[i];
        }   
    
        std::string s;
        for (size_t i = expected_num; i < up; i++) { 
            if (buf.count(i) == 0) break;
            s += buf[i];
            expected_num += 1;
            buf.erase(i);
        }
    
        stream_out().write(s);
    
        if (eof) {
            end_flag = true;
            end_num = index + data.size();
        }
    
        if (end_flag && expected_num == end_num) {
            stream_out().end_input();
        }
    }
    
    size_t StreamReassembler::unassembled_bytes() const {
        return buf.size();
    }
    
    bool StreamReassembler::empty() const {
        return buf.empty();
    }
    

    # 实验结果


    image-20230313233458047

    效率一般般,感觉可以用别的数据结构优化一下,但是有点懒了就咕了。

    #Learning Notes#Computer Networking#CS144:Computer Networking
    Last Updated: 3/15/2023, 9:05:12 AM
    Lab0:Networking warmup
    Lab2:The TCP Receiver

    ← Lab0:Networking warmup Lab2:The TCP Receiver→

    最近更新
    01
    408 计组笔记
    07-19
    02
    Dive into Deep Learning
    01-27
    03
    25 考研随记
    11-27
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式