看本文之前请先仔细浏览实验手册,要大致知道实验让我们做啥。
实验内容
详细请看实验手册。简略来说就是做一个流重组器,保证能够有序接收字符串。
要点
capacity以及_output
个人感觉重点是要理解实验指导书的这幅图以及capacity的含义到底是什么。
图一
本人一开始做的时候思路非常的乱,看实验指导书看了许多遍也未能准确把握capacity的含义,后面本人想既然实验是实现TCP中的某个部分,那么这些东西肯定会与TCP中的原理相对应,于是本人回顾了TCP原理,最后发现capacity就是滑动窗口的大小。于是本人将上图稍微进行了下修改一一下使它更便于理解。如图二所示。
图二
再来看实验代码中已经有的变量:
ByteStream _output; //!< The reassembled in-order byte stream
这个是在lab0中实现的变量,在上图中对应的就是有序但等待上层程序读取的数据。
所以我们还要添加一个私有变量,用来存放无序,但需要接收字符串以变成有序的数据。
std::map<size_t,std::string> unassembled_strings;//未排序好的字符串
本人为什么用map待会儿再解释。
push_substring函数
push_substring函数绝对是整个实验中核心,传入的字符串data,本人一开始考虑了大概6种情况,但花费了许久仔细想想,得到图二之后,发现其实就两种情况。因为需要传入字符串的区域,仅是图二中用来存放无序数据需要接收字符串,以变成有序的数据的这片区域。即第一,传入的字符串data并未落入这片区域,那么我们不需要对其进行处理;第二,如果传入的字符串全部或者部分落在了这片区域内,我们仅需要对字符串进行裁剪仅保留落入到这片区域的部分。核心代码如下:
unassembled_start为这片区域开始的位置,unassembled_capacity函数返回该区域的大小。
if(index+data.size()<unassembled_start||index>unassembled_start+unassembled_capacity())return;
//取二者最大的
size_t sub_str_index=max(index,unassembled_start);
//取二者最小的
size_t sub_str_endindex=min(index+data.size(),unassembled_start+unassembled_capacity());
string sub_str=data.substr(sub_str_index-index,sub_str_endindex-sub_str_index);
在得到sub_str字符串之后,我们插入到这片区域即可,注意该字符串可能与该区域会有重叠,所以我们要对其进行处理。
if(index+data.size()<=unassembled_start+unassembled_capacity()&&eof) _eof=true;
//将字符串插入到该区域中
insert_unassembled_string(sub_str_index,sub_str);
//将有序的数据写入到_output区域中
write_assembled_string();
insert_unassembled_string函数
以下是insert_unassembled_string函数的具体代码,相关的算法可以参考Leetcode56 Leetcode57。
https://leetcode.cn/problems/merge-intervals/description/
https://leetcode.cn/problems/insert-interval/
相信做完以上两道题,实现该函数会容易很多。
这里解释使用map的原因,map底层是用红黑树实现的,并且map自动维护了升序的序列,更便于对字符串相关重叠进行处理。
void StreamReassembler::insert_unassembled_string(size_t index,string str1){
if(str1.empty()) return;
map<size_t,string>::iterator it=unassembled_strings.find(index);
//如果找到了
if(it!=unassembled_strings.end()){
//如果大于
if(str1.size()>it->second.size()) {
unassembled_count+=str1.size()-it->second.size();
it->second=str1;
}
}
//没找到那么直接插入即可
else{
unassembled_strings.insert(make_pair(index,str1));
unassembled_count+=str1.size();
}
//做的是字符串的合并
for(it=unassembled_strings.begin();it!=unassembled_strings.end();){
map<size_t,string>::iterator it1=next(it);
if(it1==unassembled_strings.end()) break;
size_t str1_end=it->first+it->second.size();
size_t str2_end=it1->first+it1->second.size();
大于等于说明有重叠
if(str1_end>=it1->first){
//二号字符有一部分超过一号字符串
if(str1_end<str2_end){
//减去重叠部分的数量
unassembled_count-=it1->second.size()-(str2_end-str1_end);
it->second=it->second+it1->second.substr(it1->second.size()-(str2_end-str1_end),str2_end-str1_end);
}
//否则减去二号字符串
else unassembled_count-=it1->second.size();
unassembled_strings.erase(it1);
}
else{
it++;
}
}
}
write_assembled_string()函数
我们将字符串插入到无序区域,无序区域可能会变成有序,所以要将有序的字符串写入_output中。
void StreamReassembler::write_assembled_string() {
//如果非空
if(!empty()){
auto it=unassembled_strings.begin();
if(it->first==unassembled_start){
_output.write(it->second);
unassembled_count-=it->second.size();
unassembled_start+=it->second.size();
unassembled_strings.erase(it);
}
}
//如果已经空了,并且字符已经结束那么结束input
if(empty()&&_eof) _output.end_input();
}
完整代码
stream_reassembler.hh
#ifndef SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#define SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#include "byte_stream.hh"
#include <cstdint>
#include <string>
#include <map>
//! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order,
//! possibly overlapping) into an in-order byte stream.
class StreamReassembler {
private:
// Your code here -- add private members as necessary.
ByteStream _output; //!< The reassembled in-order byte stream
size_t _capacity; //!< The maximum number of byte
std::map<size_t,std::string> unassembled_strings;//未排序好的字符串
size_t unassembled_start;//未排好序到字符串开始
size_t unassembled_count;//未排序的字符串个数
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;
size_t assembled_capacity()const;
size_t unassembled_capacity() const;
//插入未排序好的字符串
void insert_unassembled_string(size_t index,std::string str1);
//插入排序好的字符串
void write_assembled_string();
};
#endif // SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
stream_reassembler.cc
#include "stream_reassembler.hh"
// Dummy implementation of a stream reassembler.
// For Lab 1, please replace with a real implementation that passes the
// automated checks run by `make check_lab1`.
// You will need to add private members to the class declaration in `stream_reassembler.hh`
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
StreamReassembler::StreamReassembler(const size_t capacity) : _output(capacity), _capacity(capacity),unassembled_strings(),unassembled_start(0),unassembled_count(0),_eof(false) {}
//! \details This function accepts a substring (aka a segment) of bytes,
//! possibly out-of-order, from the logical stream, and assembles any newly
//! contiguous substrings and writes them into the output stream in order.
void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
DUMMY_CODE(data, index, eof);
if(index+data.size()<unassembled_start||index>unassembled_start+unassembled_capacity())return;
//取二者最大的
size_t sub_str_index=max(index,unassembled_start);
//取二者最小的
size_t sub_str_endindex=min(index+data.size(),unassembled_start+unassembled_capacity());
string sub_str=data.substr(sub_str_index-index,sub_str_endindex-sub_str_index);
if(index+data.size()<=unassembled_start+unassembled_capacity()&&eof) _eof=true;
insert_unassembled_string(sub_str_index,sub_str);
write_assembled_string();
}
size_t StreamReassembler::unassembled_bytes() const { return unassembled_count; }
bool StreamReassembler::empty() const { return unassembled_strings.empty(); }
size_t StreamReassembler::assembled_capacity() const{
return _output.buffer_size();
}
size_t StreamReassembler::unassembled_capacity() const{
return _capacity-_output.buffer_size();
}
void StreamReassembler::insert_unassembled_string(size_t index,string str1){
if(str1.empty()) return;
map<size_t,string>::iterator it=unassembled_strings.find(index);
//如果找到了
if(it!=unassembled_strings.end()){
//如果大于
if(str1.size()>it->second.size()) {
unassembled_count+=str1.size()-it->second.size();
it->second=str1;
}
}
//没找到那么直接插入即可
else{
unassembled_strings.insert(make_pair(index,str1));
unassembled_count+=str1.size();
}
//做的是字符串的合并
for(it=unassembled_strings.begin();it!=unassembled_strings.end();){
map<size_t,string>::iterator it1=next(it);
if(it1==unassembled_strings.end()) break;
size_t str1_end=it->first+it->second.size();
size_t str2_end=it1->first+it1->second.size();
大于等于说明有重叠
if(str1_end>=it1->first){
//二号字符有一部分超过一号字符串
if(str1_end<str2_end){
//减去重叠部分的数量
unassembled_count-=it1->second.size()-(str2_end-str1_end);
it->second=it->second+it1->second.substr(it1->second.size()-(str2_end-str1_end),str2_end-str1_end);
}
//否则减去二号字符串
else unassembled_count-=it1->second.size();
unassembled_strings.erase(it1);
}
else{
it++;
}
}
}
void StreamReassembler::write_assembled_string() {
//如果非空
if(!empty()){
auto it=unassembled_strings.begin();
if(it->first==unassembled_start){
_output.write(it->second);
unassembled_count-=it->second.size();
unassembled_start+=it->second.size();
unassembled_strings.erase(it);
}
}
//如果已经空了,并且字符已经结束那么结束input
if(empty()&&_eof) _output.end_input();
}
实验结果
遇到的坑
1.关于如何debug的问题,本人一开始用的是GDB,但是GDB奈何本人不是很熟练,于是将GDB改成了VScode下的debug,如何设置可参考以下文章:
https://yao-yin.github.io/cs144/2021/06/20/cs144-lab-1/
2.本人一开始在写完代码之后,发现怎么样结果都是错的,后来发现lab1要用到lab0实现的东西,所以得加上lab0写的代码。并且要确保lab0的代码是正确的,这个可能得debug啥的才知道,本人个人的体验是,由于自己之前写的某些lab0的代码不够规范,即使通过了lab0的test(lab0的测试用列过少了)也会因为某些问题,导致lab1的测试结果受到影响
碎碎念
本人算是比较菜的吧,相比于很多大佬几个小时就写完了,本人花费了接近两天的时间,中途一度想过要放弃吧,思想斗争也做了很久 ~~没关系的吧,我认识的很多学长也没做cs144呀他们也不是一样也很厉害嘛~~ 反正是自己给自己加的任务,做不出来就做不出来吧,没有人会怪我的。虽然debug是很折磨,但最终还是坚持了下来,看到全部pass的那一刻成就感满满,哈哈哈,算是彩笔的自我满足吧。