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"}
而后就能够在测试源代码中打断点调试。