经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 计算机原理 » 查看文章
CS144 计算机网络 Lab1:Stream Reassembler
来源:cnblogs  作者:之一Yo  时间:2023/4/21 8:58:56  对本文有异议

前言

上一篇博客中我们完成了 Lab0,使用双端队列实现了一个字节流类 ByteStream,可以向字节流中写入数据并按写入顺序读出数据。由于网络环境的变化,发送端滑动窗口内的数据包到达接收端时可能失序,所以接收端收到数据之后不能直接写入 ByteStream 中,而是应该缓存下来并按照序号重组成正确的数据。这篇博客所介绍的 Lab1 将实现一个字节流重组器 StreamReassambler 来完成上述任务。

CS144 TCPSocket 架构

实验要求

接收方的数据情况如下图所示,蓝色部分表示已消费的数据,绿色表示已正确重组但是还没消费的数据,红色则是失序到达且还没重组的数据:

数据分类

由于接收端缓冲区大小 capacity 有限,超出容量的数据(first unacceptable 之后的数据)将被丢弃,这些被丢弃的数据包将起到流量控制的作用,可以限制发送端滑动窗口的大小。

流重组器的接口如下所示:

  1. StreamReassembler(const size_t capacity);
  2. //! \brief Receives a substring and writes any newly contiguous bytes into the stream.
  3. //!
  4. //! If accepting all the data would overflow the `capacity` of this
  5. //! `StreamReassembler`, then only the part of the data that fits will be
  6. //! accepted. If the substring is only partially accepted, then the `eof`
  7. //! will be disregarded.
  8. //!
  9. //! \param data the string being added
  10. //! \param index the index of the first byte in `data`
  11. //! \param eof whether or not this segment ends with the end of the stream
  12. void push_substring(const std::string &data, const uint64_t index, const bool eof);
  13. //! Access the reassembled byte stream
  14. const ByteStream &stream_out() const { return _output; }
  15. ByteStream &stream_out() { return _output; }
  16. //! The number of bytes in the substrings stored but not yet reassembled
  17. size_t unassembled_bytes() const;
  18. //! Is the internal state empty (other than the output stream)?
  19. bool empty() const;

其中最重要的函数就是 StreamReassambler::push_substring(),接收方收到数据之后就会调用此函数将数据保存起来。此函数接受三个参数:

  • data: 接收到的数据
  • index: 数据的第一个字节的索引,由于原始数据可能很大,超过了 TCPSegment 的容量,所以会将原始数据切分成多个片段,每个片段的第一个字节的索引就是 index,最小值为 0
  • eof:是不是最后一个数据包

三个参数中,最耐人寻味的就是 index 参数,如果只是单纯的失序到达,数据之间没有发生重叠,Lab1 就比较好做了,但是实验指导书中明确指出

May substrings overlap? Yes

这就比较难搞了,因为重叠分成两种:

  1. 前面一部分与已重组的数据发生重叠
    重叠情况1

  2. 前面不与已重组的数据发生重叠

    重叠情况2

实际上由于 data 的末尾可能超出 first unacceptable,需要对超出部分进行截断,这可能导致 eof 标志失效,但是问题不大,发送方之后会重新发送这个数据包。

代码实现

为了处理上述重叠情况,需要一个 _next_index 成员代表 first unassembled 索引,一个 _unassembles 双端队列代表 first unassembledfirst unacceptable 之间的数据,由于里面可能只有一部分数据是有效的,所以用一个遮罩 _unassembled_mask 指出哪些数据是有效但是还没重组的。

  1. class StreamReassembler {
  2. private:
  3. ByteStream _output; //!< The reassembled in-order byte stream
  4. size_t _capacity; //!< The maximum number of bytes
  5. std::deque<char> _unassembles{};
  6. std::deque<bool> _unassemble_mask{};
  7. size_t _unassambled_bytes{0};
  8. uint64_t _next_index{0};
  9. bool _is_eof{false};
  10. /** @brief 将数据写入未重组队列中
  11. * @param data 将被写入的字符串
  12. * @param dstart 字符串开始写入的位置
  13. * @param len 写入的长度
  14. * @param astart 队列中开始写入的位置
  15. */
  16. void write_unassamble(const std::string &data, size_t dstart, size_t len, size_t astart);
  17. /** @brief 重组数据
  18. */
  19. void assemble();
  20. public:
  21. StreamReassembler(const size_t capacity);
  22. //! \brief Receives a substring and writes any newly contiguous bytes into the stream.
  23. void push_substring(const std::string &data, const uint64_t index, const bool eof);
  24. //! \name Access the reassembled byte stream
  25. const ByteStream &stream_out() const { return _output; }
  26. ByteStream &stream_out() { return _output; }
  27. //! The number of bytes in the substrings stored but not yet reassembled
  28. size_t unassembled_bytes() const;
  29. bool empty() const;
  30. };

收到数据时,先将不重叠的数据写入 _unassembles 队列中,之后调用 StreamReassabler::assemble() 函数重组队列中的连续数据,并更新 _next_index

  1. StreamReassembler::StreamReassembler(const size_t capacity)
  2. : _output(capacity), _capacity(capacity), _unassembles(capacity, '\0'), _unassemble_mask(capacity, false) {}
  3. //! \details This function accepts a substring (aka a segment) of bytes,
  4. //! possibly out-of-order, from the logical stream, and assembles any newly
  5. //! contiguous substrings and writes them into the output stream in order.
  6. void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
  7. if (index > _next_index + _capacity)
  8. return;
  9. if (eof)
  10. _is_eof = true;
  11. if (eof && empty() && data.empty()) {
  12. _output.end_input();
  13. return;
  14. }
  15. auto end_index = data.size() + index;
  16. // 新数据在后面
  17. if (index >= _next_index) {
  18. auto astart = index - _next_index;
  19. auto len = min(_output.remaining_capacity() - astart, data.size());
  20. if (len < data.size())
  21. _is_eof = false;
  22. write_unassamble(data, 0, len, astart);
  23. }
  24. // 新数据与已重组的数据部分重叠
  25. else if (end_index > _next_index) {
  26. auto dstart = _next_index - index;
  27. auto len = min(_output.remaining_capacity(), data.size() - dstart);
  28. if (len < data.size() - dstart)
  29. _is_eof = false;
  30. write_unassamble(data, dstart, len, 0);
  31. }
  32. // 最后合并数据
  33. assemble();
  34. if (_is_eof && empty())
  35. _output.end_input();
  36. }
  37. void StreamReassembler::write_unassamble(const string &data, size_t dstart, size_t len, size_t astart) {
  38. for (size_t i = 0; i < len; ++i) {
  39. if (_unassemble_mask[i + astart])
  40. continue;
  41. _unassembles[i + astart] = data[dstart + i];
  42. _unassemble_mask[i + astart] = true;
  43. _unassambled_bytes++;
  44. }
  45. }
  46. void StreamReassembler::assemble() {
  47. string s;
  48. while (_unassemble_mask.front()) {
  49. s.push_back(_unassembles.front());
  50. _unassembles.pop_front();
  51. _unassemble_mask.pop_front();
  52. _unassembles.push_back('\0');
  53. _unassemble_mask.push_back(false);
  54. }
  55. if (s.empty())
  56. return;
  57. _output.write(s);
  58. _next_index += s.size();
  59. _unassambled_bytes -= s.size();
  60. }
  61. size_t StreamReassembler::unassembled_bytes() const { return _unassambled_bytes; }
  62. bool StreamReassembler::empty() const { return _unassambled_bytes == 0; }

在命令行中输入:

  1. cd build
  2. make -j8
  3. make check_lab1

可以看到测试用例也全部通过了:

测试通过

调试代码

由于使用代码编辑器的是 VSCode,所以这里给出在 VSCode 中调试项目代码的方式。

tasks.json

首先在项目目录下创建 .vscode 文件夹,并新建一个 tasks.json 文件,在里面写入下述内容:

  1. {
  2. "tasks": [
  3. {
  4. "type": "shell",
  5. "label": "cmake",
  6. "command": "cd build && cmake .. -DCMAKE_BUILD_TYPE=Debug",
  7. "detail": "CMake 生成 Makefile",
  8. "args": [],
  9. "problemMatcher": "$gcc"
  10. },
  11. {
  12. "type": "shell",
  13. "label": "build",
  14. "command": "cd build && make -j8",
  15. "detail": "编译项目",
  16. "args": [],
  17. "problemMatcher": "$gcc"
  18. },
  19. ],
  20. "version": "2.0.0"
  21. }

这里主要配置了两个任务,一个调用 CMake 生成 Makefile,一个编译 Makefile。在 VSCode 中按下 Alt + T + R,就能在任务列表中看到这两个任务,点击之后就能执行。

任务列表

launch.json

.vscode 文件夹中新建 launch.json,并写入下述内容:

  1. {
  2. // Use IntelliSense to learn about possible attributes.
  3. // Hover to view descriptions of existing attributes.
  4. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  5. "version": "0.2.0",
  6. "configurations": [
  7. {
  8. "name": "debug lab test",
  9. "type": "cppdbg",
  10. "request": "launch",
  11. "program": "${workspaceFolder}/build/tests/${fileBasenameNoExtension}",
  12. "args": [],
  13. "stopAtEntry": false,
  14. "cwd": "${workspaceFolder}",
  15. "environment": [],
  16. "externalConsole": false,
  17. "MIMode": "gdb",
  18. "setupCommands": [
  19. {
  20. "description": "Enable pretty-printing for gdb",
  21. "text": "-enable-pretty-printing",
  22. "ignoreFailures": true
  23. }
  24. ],
  25. "miDebuggerPath": "/usr/bin/gdb"
  26. },
  27. {
  28. "name": "debug current file",
  29. "type": "cppdbg",
  30. "request": "launch",
  31. "program": "${fileDirname}/${fileBasenameNoExtension}",
  32. "args": [],
  33. "stopAtEntry": false,
  34. "cwd": "${workspaceFolder}",
  35. "environment": [],
  36. "externalConsole": false,
  37. "MIMode": "gdb",
  38. "setupCommands": [
  39. {
  40. "description": "Enable pretty-printing for gdb",
  41. "text": "-enable-pretty-printing",
  42. "ignoreFailures": true
  43. }
  44. ],
  45. "preLaunchTask": "C/C++: g++ build active file",
  46. "miDebuggerPath": "/usr/bin/gdb"
  47. }
  48. ]
  49. }

之后打开一个测试用例,比如 tests/fsm_stream_reassembler_seq.cc,转到 debug 标签页,在代码中打下断点, 点击绿色按钮就能开始调试了:

调试测试用例

调试效果如下图所示:

调试效果

后记

通过这次实验,可以加深对接收端数据重组和分组序号的了解,期待后面的几个实验,以上~~

原文链接:https://www.cnblogs.com/zhiyiYo/p/17338730.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号