CS 144: Introduction to Computer Networking, Fall 2020
https://cs144.github.io/My Repo
https://github.com/wine99/cs1…
工作
TCP 接受方接管到乱序且可能重叠的报文段,StreamReassembler 须要将收到的报文段按状况送入 ByteStream (lab0 实现的),或抛弃,或暂存(在适合的时候重组送入 ByteStream)。
留神点:
- 报文段蕴含索引、长度、内容,lab1 的索引从 0 开始增长,不会溢出绕回。
- 任何报文段,包含新收到的和暂存的,只有能够,就应该立即送入 ByteStream(可能须要手动重组和去重叠)。
- 容量的限度如下图所示。
思路
- 用 set 来暂存报文段,依照报文段的 index 大小比照重载 < 运算符。
- 用 _eof 来保留是否曾经收到过有 EOF 标识的段。
- 收到新段时,通过比照新段的 index、length 和 _first_unacceptable,_first_unassembled 对新段进行必要的剪切,而后解决重叠(调用 _handle_overlap)。
- 解决重叠的逻辑:遍历每个暂存段,如果与新段产生重叠,合并暂存段与新段(调用 _merge_seg,总是往新段上合并,合并后删除重叠的暂存段)。
- 合并段的办法:分类探讨,两个段总共会有四种不同的重叠状况,别离解决。
- 解决完重叠后,调用 _stitch_output:遍历所有暂存段,将可合并的暂存段接入 ByteStream 中。
- 最初,当 _eof 为真且 unassembled_bytes 为 0 时,调用 ByteSteram 的 end_input。
代码
stream_reassembler.hh:
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 bytes
size_t _first_unread = 0;
size_t _first_unassembled = 0;
size_t _first_unacceptable;
bool _eof = false;
struct seg {
size_t index;
size_t length;
std::string data;
bool operator<(const seg t) const {return index < t.index;}
};
std::set<seg> _stored_segs = {};
void _add_new_seg(seg &new_seg, const bool eof);
void _handle_overlap(seg &new_seg);
void _stitch_output();
void _stitch_one_seg(const seg &new_seg);
void _merge_seg(seg &new_seg, const seg &other);
public:
stream_reassembler.cc:
留神 _add_new_seg 中对于 EOF 的解决细节。
#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), _first_unacceptable(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) {_first_unread = _output.bytes_read();
_first_unacceptable = _first_unread + _capacity;
seg new_seg = {index, data.length(), data};
_add_new_seg(new_seg, eof);
_stitch_output();
if (empty() && _eof)
_output.end_input();}
void StreamReassembler::_add_new_seg(seg &new_seg, const bool eof) {
// check capacity limit, if unmeet limit, return
// cut the bytes in NEW_SEG that will overflow the _CAPACITY
// note that the EOF should also be cut
// cut the bytes in NEW_SEG that are already in _OUTPUT
// _HANDLE_OVERLAP()
// update _EOF
if (new_seg.index >= _first_unacceptable)
return;
bool eof_of_this_seg = eof;
if (int overflow_bytes = new_seg.index + new_seg.length - _first_unacceptable; overflow_bytes > 0) {
int new_length = new_seg.length - overflow_bytes;
if (new_length <= 0)
return;
eof_of_this_seg = false;
new_seg.length = new_length;
new_seg.data = new_seg.data.substr(0, new_seg.length);
}
if (new_seg.index < _first_unassembled) {int new_length = new_seg.length - (_first_unassembled - new_seg.index);
if (new_length <= 0)
return;
new_seg.length = new_length;
new_seg.data = new_seg.data.substr(_first_unassembled - new_seg.index, new_seg.length);
new_seg.index = _first_unassembled;
}
_handle_overlap(new_seg);
// if EOF was received before, it should remain valid
_eof = _eof || eof_of_this_seg;
}
void StreamReassembler::_handle_overlap(seg &new_seg) {for (auto it = _stored_segs.begin(); it != _stored_segs.end();) {
auto next_it = ++it;
--it;
if ((new_seg.index >= it->index && new_seg.index < it->index + it->length) ||
(it->index >= new_seg.index && it->index < new_seg.index + new_seg.length)) {_merge_seg(new_seg, *it);
_stored_segs.erase(it);
}
it = next_it;
}
_stored_segs.insert(new_seg);
}
void StreamReassembler::_stitch_output() {
// _FIRST_UNASSEMBLED is the expected next index_FIRST_UNASSEMBLED
// compare _STORED_SEGS.begin()->index with
// if equals, then _STITCH_ONE_SEG() and erase this seg from set
// continue compare until not equal or empty
while (!_stored_segs.empty() && _stored_segs.begin()->index == _first_unassembled) {_stitch_one_seg(*_stored_segs.begin());
_stored_segs.erase(_stored_segs.begin());
}
}
void StreamReassembler::_stitch_one_seg(const seg &new_seg) {
// write string of NEW_SEG into _OUTPUT
// update _FIRST_UNASSEMBLED
_output.write(new_seg.data);
_first_unassembled += new_seg.length;
// both way of updating _FIRST_UNASSEMBLED is ok
// _first_unassembled = _output.bytes_written();}
void StreamReassembler::_merge_seg(seg &new_seg, const seg &other) {
size_t n_index = new_seg.index;
size_t n_end = new_seg.index + new_seg.length;
size_t o_index = other.index;
size_t o_end = other.index + other.length;
string new_data;
if (n_index <= o_index && n_end <= o_end) {new_data = new_seg.data + other.data.substr(n_end - o_index, n_end - o_end);
} else if (n_index <= o_index && n_end >= o_end) {new_data = new_seg.data;} else if (n_index >= o_index && n_end <= o_end) {
new_data =
other.data.substr(0, n_index - o_index) + new_seg.data + other.data.substr(n_end - o_index, n_end - o_end);
} else /* if (n_index >= o_index && n_end <= o_end) */ {new_data = other.data.substr(0, n_index - o_index) + new_seg.data;
}
new_seg.index = n_index < o_index ? n_index : o_index;
new_seg.length = (n_end > o_end ? n_end : o_end) - new_seg.index;
new_seg.data = new_data;
}
size_t StreamReassembler::unassembled_bytes() const {
size_t unassembled_bytes = 0;
for (auto it = _stored_segs.begin(); it != _stored_segs.end(); ++it)
unassembled_bytes += it->length;
return unassembled_bytes;
}
bool StreamReassembler::empty() const { return unassembled_bytes() == 0; }
VS Code 调试办法
测试样例对应的源文件在 ./tests 文件夹中,如下图所示。
对应的可执行文件在 ./build/tests 中。
通常 VSC 的 launch.json 中主动生成了一个名为 debug current file 的 launch targe,它有一个前置工作 C/C++: g++ build active file。仿照该 lanuch targe,能够写一个名为 debug lab test 的 launch targe。如下所示。
launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "debug lab test",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/tests/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "debug webget",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/apps/webget",
"args": ["cs144.keithw.org", "/hello"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
// "preLaunchTask": "C/C++: g++ build active file",
"preLaunchTask": "build project",
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "debug current file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: g++ build active file",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}
tasks.json:
{
"tasks": [
{
"type": "shell",
"label": "C/C++: g++ build active file",
"command": "/usr/bin/g++",
"args": [
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {"cwd": "${workspaceFolder}"
},
"problemMatcher": ["$gcc"],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "shell",
"label": "build project",
"command": "cd build && make -j8",
"args": [],},
],
"version": "2.0.0"
}
而后就能够在测试源代码中打断点调试。