CS144 计算机网络 lab1
上一学期刚学习了计算机网络这门课程,感觉还是挺有意思的,了解到CS144的lab所以便打算利用假期练练手,顺便也尝试着开始写博客。代码风格还很新手,多多包涵,欢迎讨论,不过估摸着也不会有什么阅读量,所以权且当作我个人的一个记录了。
初步分析
切入正题,lab1的核心思路都在文档里给的这张图上了:
蓝色的部分是已经从字节流中读出的,绿色的部分是已经接收到但还没有被读出,红色的部分是已经被接收但还没被组装。绿色和红色的这一块构成了容量_capacity。
设置成员变量
class StreamReassembler {
private:
// Your code here -- add private members as necessary.
size_t _first_unread; //第一个还未被读的字节索引
bool end; //结束标志
size_t _end_index; //字节流结束的索引
std::map<size_t, char> mp; //暂存区
ByteStream _output; //!< The reassembled in-order byte stream
size_t _capacity; //!< The maximum number of bytes
}
初始代码给出了字节流_output,对于未读的字节流我们设置first_unread来进行标识,对于未组装的字节流需要进行暂存,这里就使用了map来进行index对应字符的键值对存储。除此之外,结束标志用end进行标识,还需设置一个字节流结束的索引。当然,这些成员变量一开始也不会考虑得面面俱到,在代码实现的过程中感觉有新的变量需要再在这里添加。
功能分析
1.字节流和读入的子串分析
我们首先要搞清楚_capacity的构成,它是由字节流_output的容量,暂存区的容量以及最新需要加入进来的子串来构成的。而_output里的东西并不是只进不出的,这也是我在做lab1的时候很久没有意识到的一个点,导致花了很长时间在上面,直到研究了测试代码才发现
这里的BytesAvailable就是模拟的从字节流中读出数据。
弄清楚了这个,我们就可以把目光放到我们需要实现的函数上去了。这个函数的功能是push一个子串,告诉我们起始的index以及数据内容data。大致会出现以下两种情况:
-
index比first_unread小;
举个例子,一开始我们读入了"abc",此时first_unread为3,然后进来一个index为2的数据"cdef"。这时候我们就需要将重复的地方切除掉,让进来的数据变为"def"。当然还有特殊情况,进来一个index为0的"ab"的话就直接将其优化成空字符串就行了。
size_t data_len = data.length(); //数据的长度 string valid_data = data; bool _eof = eof; size_t idx = index; //原来的index是常量无法改变,重新赋给一个idx if(idx < _first_unread){ if(_first_unread - idx >= data_len){ idx = _first_unread; data_len = 0; valid_data = ""; } else { valid_data = valid_data.substr(_first_unread - idx); idx = _first_unread; data_len = valid_data.length(); } }
-
index大于等于first_unread。
第一种情况的处理完后就会变为第二种情况,这时候两条路便归一了。接着考虑,要是data过长,也会造成溢出:
index加上len超出了capacity;
比如容量为8,先push了abc;
然后来了个index为6的ghX,
此时output容量3加上暂存区容量0加len并未超容,但push的ghX是会超容的。
我当时犯的错误就在此处。我天真地以为,只需index+len和_capacity进行一下判断即可,但是注意一下定义,capacity是output,暂存区,以及新进来的子串三者容量之和。
举个例子(其实就是测试样例):
初始化:capacity为2;
操作1:push一个index为0的"ab";
操作2:从output中读出"ab";
操作3:再push一个index为2的"cd"。
如果单纯用index+data_len和capacity进行比较,操作3就不会执行成功。但实际上output的容量和暂存区的容量为0,所以是可以进行操作3的。
if(idx + data_len + _output.buffer_size() - _first_unread > _capacity){
data_len = _capacity - _output.buffer_size() - idx + _first_unread;
valid_data = valid_data.substr(0, data_len);
_eof = false;
}
修改_eof的原因是,只要进入了if语句,数据串就一定会被切割,哪怕给的_eof为true在切割之后都会消失,所以改为false。
2.暂存区分析
对于暂存区,我们可以采用如下的方法来处理:
无脑往暂存区加东西,加完用first_unread扫暂存区,符合条件就往output中去写,写完就将其删除。
还是上面的例子,容量8.
第一次操作,index 0,“abc” -> 暂存区写进abc,first unread从0开始
读暂存区,0,1,2都能读到东西,写进output中,3读不到了,first unread
就暂时为3。此时暂存区的’a’,‘b’,'c’都已经被删了。
第二次操作,index 6,“ghX” -> 先进行一个处理判断,data变为了"gh";
写进暂存区,暂存区现在有"gh"。unread为3。从3开始读暂存区,
读不到东西,操作结束。
第三次操作,index 4,“efg” -> 无脑写进暂存区,暂存区现在有"efgh",
uread为3。
第四次操作,index 3,“d” -> 无脑写进暂存区,现在有"defgh"。
unread从3一个一个读,读到’h’结束,此时unread为8。'defgh’都被写入了output中。
//下面开始无脑往暂存区写东西
for(size_t i = idx;i<idx + valid_data.length();i++){
mp[i] = valid_data[i - idx];
}
string str = "";
while (mp.find(_first_unread)!=mp.end())
{
str += mp[_first_unread];
mp.erase(_first_unread);
_first_unread++;
}
if(str.size()>0){
_output.write(str);
}
3.结束的判断
在向暂存区写东西前,我们需要对是否输入结束进行一个判断:eof为true便是结束,给成员变量end赋为true,结束索引为进行修改后的idx+data_len。
if(_eof){
end = true;
_end_index = idx + data_len;
}
暂存区写完后,end为true的情况下first_unread和_end_index若是相等便是结束了。
if(end && _first_unread == _end_index){
_output.end_input();//停止写入
}
测试及代码
测试如下:
全部通过。
具体的测试代码在tests文件夹下可找到,可以自己手动修改调试。
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.
size_t _first_unread; //第一个还未被读的字节索引
bool end; //结束标志
size_t _end_index; //字节流结束的索引
std::map<size_t, char> mp;
ByteStream _output; //!< The reassembled in-order byte stream
size_t _capacity; //!< The maximum number of bytes
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;
};
#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`
#include<iostream>
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
StreamReassembler::StreamReassembler(const size_t capacity) : _first_unread(0), end(false), _end_index(0), mp{}, _output(capacity), _capacity(capacity){}
//! \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) {
size_t data_len = data.length(); //数据的长度
string valid_data = data;
bool _eof = eof;
size_t idx = index; //原来的index是常量无法改变,重新赋给一个idx
if(idx < _first_unread){
if(_first_unread - idx >= data_len){
idx = _first_unread;
data_len = 0;
valid_data = "";
}
else
{
valid_data = valid_data.substr(_first_unread - idx);
// cout<<valid_data<<endl;
idx = _first_unread;
data_len = valid_data.length();
}
}
if(idx + data_len + _output.buffer_size() - _first_unread> _capacity){
data_len = _capacity - _output.buffer_size() - idx + _first_unread;
valid_data = valid_data.substr(0, data_len);
_eof = false;
}
if(_eof){
end = true;
_end_index = idx + data_len;
}
//下面开始无脑往暂存区写东西
for(size_t i = idx;i<idx + valid_data.length();i++){
mp[i] = valid_data[i - idx];
}
string str = "";
while (mp.find(_first_unread)!=mp.end())
{
str += mp[_first_unread];
mp.erase(_first_unread);
_first_unread++;
}
if(str.size()>0){
_output.write(str);
}
if(end && _first_unread == _end_index){
_output.end_input();
}
}
size_t StreamReassembler::unassembled_bytes() const {
return mp.size();
}
bool StreamReassembler::empty() const {
return mp.size() == 0;
}