CS144 lab1实验要点记录及总结

CS144 lab1 实验问题记录


1.碎片可能交叉或重叠。

2.如果某次新碎片到达后字节流的开头部分被凑齐,那就应当立刻把凑齐的部分立刻写入到_output中。即对应讲义中的:

When should bytes be written to the stream?

As soon as possible. The only situation in which a byte should not be in the stream is that when there is a byte before it that has not been “pushed” yet.

3.碎片可能是一个只包含EOF标志的空串

4.LAB0的顺序字节流和LAB1的流重组器各有各的容量限制。流重组器把字节流写满后,只有当字节流腾出空后才能继续写,相当于字节流满时流重组器出口被“堵住”了。同样当流重组器容量满了后自身也无法被写入新数据,此时到来的新碎片只能被丢弃掉。

第一反应联想到了操作系统里的进程内存管理,用一个二叉排序树来记录每个碎片的索引、长度,排序规则为按索引值升序,每次插入新碎片时判断能不能和前后碎片进行合并。流的内容则可以用一个数组来做缓冲区,或者干脆一块存储在二叉树的节点里。不过还是因为“Modern C++”的缘故,我再次退而求其次用std::list代替之。等我哼哧哼哧花了好几个小时写完LAB1后,又哼哧哼哧得改了一众BUG后,**才想起std::set底层就是用红黑树实现的,**可以直接拿来用。

用一个block_node结构体来存放每个碎片的索引、长度、内容。又因为set排序实现基于对应节点类型的小于运算符规则,所以我把**block_node结构体的小于运算符重载为按索引值升序**。再简单说下我的push_substring处理流程:

  • 容量判断:满了就立刻返回。
  • 处理子串的冗余前缀:如果子串包含已经被写入字节流的部分,就把这部分剪掉。
  • 合并子串:运用set自带的lowerbound快速确定插入位置,前后重复比较,用个自己写的子函数判断重叠的字顺便合并之。
  • 写入字节流:如果流重组器头部非空,就把头部写入字节流,并更新指示头部的游标。
  • EOF判断
class StreamReassembler {
  private:
    // Your code here -- add private members as necessary.
    struct block_node {
        size_t begin = 0;
        size_t length = 0;
        std::string data = "";
        bool operator<(const block_node t) const { return begin < t.begin; }
    };
    std::set<block_node> _blocks = {};
    std::vector<char> _buffer = {};
    size_t _unassembled_byte = 0;
    size_t _head_index = 0;
    bool _eof_flag = false;
    ByteStream _output;  //!< The reassembled in-order byte stream
    size_t _capacity;    //!< The maximum number of bytes

    //! merge elm2 to elm1, return merged bytes
    long merge_block(block_node &elm1, const block_node &elm2);
    //......

上面就是写的类的内容,其中block_node结构体重载了运算符号,结构体block_node还定义了一个小于运算符(operator<),用于比较两个block_node对象的大小。该小于运算符是成员函数,它接受一个const block_node类型的参数t,并返回一个bool类型的值。它的实现比较简单,仅仅是比较两个block_node对象的begin成员变量的大小,并返回比较结果。这个小于运算符可以用于对block_node对象进行排序,例如在使用std::set容器时,可以按照block_node对象的begin值进行排序。

问题1 实际工程中使用vector,set记得include 相关头文件

在实验中,使用vector和set在.h文件中没有报错,但是在.c文件中就无法链接相关成员变量

问题2 lower_bound到底是如何起作用的

在这段代码中,_blocks是一个std::set容器,用于存储block_node类型的元素。segment结构体定义了三个成员变量begin、length和data,并重载了小于号运算符,以便可以将block_node对象插入到std::set容器中

lower_bound()函数的作用是在_blocks容器中查找第一个大于等于给定elm的元素,并返回指向该元素的迭代器。由于_blocks是一个std::set容器,它的元素默认按照小于号运算符进行排序,因此lower_bound()函数查找的是第一个大于等于给定elm的元素。

具体来说,lower_bound()函数会从容器的起始位置开始,依次比较每个元素和给定的elm,直到找到第一个大于等于elm的元素。在这个过程中,lower_bound()函数会利用block_node结构体中重载的小于号运算符进行比较。如果给定的elm小于容器中的某个元素,那么这个元素就是第一个大于等于elm的元素,lower_bound()函数返回指向这个元素的迭代器。

因此,iter = _blocks.lower_bound(elm)语句的作用是在_blocks容器中查找第一个大于等于elm的元素,并将指向该元素的迭代器赋值给iter。然后程序将使用iter指向的元素进行操作,例如与elm合并或从容器中删除等。

问题3 如何解决新来的串的重叠

    do {
        // merge next
        long merged_bytes = 0;
        //lower_bound()函数的作用是在_blocks容器中查找第一个大于等于给定elm的元素
        auto iter = _blocks.lower_bound(elm);
        //? 为什么要做循环?
        //因为到来的子串头部后方可能横跨多个子串
        //        4|5|6|7|8|9|10
        //0|1|2|3|4| |6|7|   |10|11|12|13|14
        //例如上面这个例子我合并了|6|7|之后后方还有一个需要合并的
        while (iter != _blocks.end() && (merged_bytes = merge_block(elm, *iter)) >= 0) {
            _unassembled_byte -= merged_bytes;
            _blocks.erase(iter);
            iter = _blocks.lower_bound(elm);
        }
        // merge prev
        //遍历前面的子串看看前方的合并情况, 这里就没有办法了,只能把前面的子串全部遍历一遍看看能否合并(因为对应lower_bound只能找到begin)
        if (iter == _blocks.begin()) {
            break;
        }
        iter--;
        while ((merged_bytes = merge_block(elm, *iter)) >= 0) {
            _unassembled_byte -= merged_bytes;
            _blocks.erase(iter);//删除iter指向的segment
            iter = _blocks.lower_bound(elm);//?因为被删除了所以重新指定一下位置
            if (iter == _blocks.begin()) {
                break;
            }
            iter--;
        }
    } while (false);
    _blocks.insert(elm);

问题4 迭代器作为函数参数传入的要点

long StreamReassembler::mergeTwoSegment(segment& str1, segment& str2)

mergeTwosegment(elm, *iter)

为什么我不能这样调用函数,必须把函数的第二个参数加上const修饰符才行

如果 mergeTwoSegment 函数的定义为 long StreamReassembler::mergeTwoSegment(segment& str1, segment& str2),那么在调用函数时,参数 str2 会被视为非常量引用,*iter 是一个迭代器,它所指向的对象是一个常量对象。所以,将 *iter 作为第二个参数传递给 mergeTwoSegment 函数时,会产生一个编译错误,提示无法将常量对象转换为非常量引用。

为了解决这个问题,需要将 mergeTwoSegment 函数的第二个参数声明为 const segment& 类型,这样就可以接受常量对象作为参数了。因此,将 mergeTwoSegment 函数的定义修改为 long StreamReassembler::mergeTwoSegment(segment& str1, const segment& str2),就可以将 *iter 作为第二个参数传递给函数了。

需要注意的是,在修改 mergeTwoSegment 函数的定义之后,如果函数中需要修改 str2 对象的值,就需要先将其复制一份,在副本上进行修改,而不能直接修改原对象的值,因为原对象是一个常量引用。

问题5 忽略了两个报文段之间不一定非要交叉或者重叠才merge,两个报文段之间紧挨着也要merge到一起

不能是大于号,必须是>=号

问题6 对goto语句的理解错误

#include<iostream>
#include<vector>
using namespace std;
int main(){
    cout << 12 << endl;
    int a = 1;
    if (a == 2)
        goto JUDGE;
    else {
        cout << 1 << endl;
    }
    cout << 32 << endl;

    JUDGE:
    cout << "pitch" << endl;
    上面的示例代码,无论goto语句是否执行都会输出“pitch”

同理

if (index + data.size() <= _head_index) {  
	goto JUDGE_EOF;
}

新来的报文段可能含有EOF但是前面的报文段还没发送

所以在运行完合并等操作之后,还会将eof_flag设为true;

问题7 括号运算优先级

(merged_bytes = merge_block(elm, *iter) >= 0))
(merged_bytes = merge_block(elm, *iter)) >= 0)

我写的代码是上面的形式 >=的优先级比赋值运算符高,所以merged_bytes 总是等于1,也就是>=运算符得到的结果为TRUE

问题8 迭代器寻找的错误写法

        while ((merged_bytes = mergeTwoSegment(elm, *iter)) >= 0) {  //?為什麼要加=呢,經過debug之后发现merge两个不同的段,这两个段可能没有重叠,返回的merged_bytes当然是0但是肯定有合并操作
            _unassembled_byte -= merged_bytes;
            //!!!!!!!!!!!!!!!
            auto temp = iter;
            _segments.erase(temp);
            iter--;
            if (iter == _segments.begin()) {
                break;
            }
            // _segments.erase(iter);
            // iter = _segments.lower_bound(elm);
            // if (iter == _segments.begin()) {
            //     break;
            // }
            // iter--;
        }

itertemp 都指向同一个元素。当您使用 _segments.erase(temp)_segments 中删除该元素时,iter 迭代器仍然指向原来的位置,但是这个位置现在已经是无效的了,因为元素已经被删除了。(调试发现元素是从segment里面删掉了,但是iter指向的仍然是原来的元素没有变化,导致又会执行一次错误的merge,再次去segment里面删除temp时,发现里面并没有这个迭代器,导致删除错误)这种情况下,使用 iter-- 来更新 iter 的值是不安全的,因为它可能会导致指向无效内存地址的未定义行为。

为了避免这种情况,可以在调用 erase() 函数之前将 iter 复制到另一个迭代器中,然后在删除元素后使用该迭代器来更新 iter 的值。例如,您可以这样修改代码:

代码的巧妙写法

        while ((merged_bytes = mergeTwoSegment(elm, *iter)) >= 0) {  //?為什麼要加=呢,經過debug之后发现merge两个不同的段,这两个段可能没有重叠,返回的merged_bytes当然是0但是肯定有合并操作
            _unassembled_byte -= merged_bytes;
            //!!!!!!!!!!!!!!!
            _segments.erase(iter);
            iter = _segments.lower_bound(elm);//
            if (iter == _segments.begin()) {
                break;
            }
            iter--;
        }

显然前面我们已经做过了后方的交叉和重叠的融合操作,这时候使用lower_bound 就定位到了当前报文段不重叠的下一个报文段,或者定位到segment.end();

这时候在执行iter-- 就跳到了前面的元素,在判断前面的元素和当前报文段是否有重叠。

代码优化

        iter--;
        if ((merged_bytes = mergeTwoSegment(elm, *iter)) >= 0) {  //?為什麼要加=呢,經過debug之后发现merge两个不同的段,这两个段可能没有重叠,返回的merged_bytes当然是0但是肯定有合并操作
            _unassembled_byte -= merged_bytes;
            //!!!!!!!!!!!!!!!
            _segments.erase(iter);
            // iter = _segments.lower_bound(elm);//
            // if (iter == _segments.begin()) {
            //     break;
            // }
            // iter--;
        }

执行过后方的交叉和融合合并之后,iter的位置指向了与当前报文段不相交的一个后方的segment,使用iter–便到了前方的segment,这个segment要么有交叉重叠,要么没有,不需要进行while循环。所以直接将原来代码中的while变成了if

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
CS144是一门名为"Introduction to Computer Networking"的课程,引用显示您所参加的是2021年秋季学期的版本。关于cs144的2023年的信息,我们需要参考引用和引用中提供的运行结果和命令含义。 从引用中的运行结果可以看出,telnet程序成功连接到了cs144.keithw.org服务器,并发送了一个GET请求,请求路径为/hello。服务器返回了200 OK的状态码,表示请求成功。响应中的内容为"Hello, CS144!"。 根据引用中对telnet命令的解释,telnet程序在主机和服务器之间打开了一个可靠的字节流连接,连接的应用进程是http。 综上所述,cs144在2023年的运行结果是成功连接到了cs144.keithw.org服务器,并获取到了服务器返回的"Hello, CS144!"的消息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【斯坦福计网CS144项目】环境配置 & Lab0: ByteStream](https://blog.csdn.net/Altair_alpha/article/details/125010070)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【CS144】lab0](https://blog.csdn.net/xsf_1903239203/article/details/130416129)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值