CS144 计算机网络 Lab1:Stream Reassembler

news2024/12/23 3:35:06

前言

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

实验要求

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

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

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

复制StreamReassembler(const size_t capacity);

//! \brief Receives a substring and writes any newly contiguous bytes into the stream.
//!
//! If accepting all the data would overflow the `capacity` of this
//! `StreamReassembler`, then only the part of the data that fits will be
//! accepted. If the substring is only partially accepted, then the `eof`
//! will be disregarded.
//!
//! \param data the string being added
//! \param index the index of the first byte in `data`
//! \param eof whether or not this segment ends with the end of the stream
void push_substring(const std::string &data, const uint64_t index, const bool eof);

//! 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
size_t unassembled_bytes() const;

//! Is the internal state empty (other than the output stream)?
bool empty() const;

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

  • data: 接收到的数据
  • index: 数据的第一个字节的索引,由于原始数据可能很大,超过了 TCPSegment 的容量,所以会将原始数据切分成多个片段,每个片段的第一个字节的索引就是 index,最小值为 0。请注意,这个 index 是流索引,不是报文段包头上的那个序列号,二者的关系将在 Lab2 中指出。
  • eof:是不是最后一个数据包

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

May substrings overlap? Yes

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

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

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

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

代码实现

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

复制class StreamReassembler {
  private:
    ByteStream _output;  //!< The reassembled in-order byte stream
    size_t _capacity;    //!< The maximum number of bytes
    std::deque<char> _unassembles{};
    std::deque<bool> _unassemble_mask{};
    size_t _unassambled_bytes{0};
    uint64_t _next_index{0};
    bool _is_eof{false};

    /** @brief 将数据写入未重组队列中
     * @param data 将被写入的字符串
     * @param dstart 字符串开始写入的位置
     * @param len 写入的长度
     * @param astart 队列中开始写入的位置
     */
    void write_unassamble(const std::string &data, size_t dstart, size_t len, size_t astart);

    /** @brief 重组数据
     */
    void assemble();

  public:
    StreamReassembler(const size_t capacity);

    //! \brief Receives a substring and writes any newly contiguous bytes into the 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
    size_t unassembled_bytes() const;

    bool empty() const;
};

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

复制StreamReassembler::StreamReassembler(const size_t capacity)
    : _output(capacity), _capacity(capacity), _unassembles(capacity, '\0'), _unassemble_mask(capacity, 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) {
    if (index > _next_index + _capacity)
        return;

    if (eof)
        _is_eof = true;

    if (_is_eof && empty() && data.empty()) {
        _output.end_input();
        return;
    }

    auto end_index = data.size() + index;

    // 新数据在后面
    if (index >= _next_index) {
        auto astart = index - _next_index;
        auto len = min(_output.remaining_capacity() - astart, data.size());
        if (len < data.size())
            _is_eof = false;

        write_unassamble(data, 0, len, astart);
    }
    // 新数据与已重组的数据部分重叠
    else if (end_index > _next_index) {
        auto dstart = _next_index - index;
        auto len = min(_output.remaining_capacity(), data.size() - dstart);
        if (len < data.size() - dstart)
            _is_eof = false;

        write_unassamble(data, dstart, len, 0);
    }

    // 最后合并数据
    assemble();
    if (_is_eof && empty())
        _output.end_input();
}

void StreamReassembler::write_unassamble(const string &data, size_t dstart, size_t len, size_t astart) {
    for (size_t i = 0; i < len; ++i) {
        if (_unassemble_mask[i + astart])
            continue;

        _unassembles[i + astart] = data[dstart + i];
        _unassemble_mask[i + astart] = true;
        _unassambled_bytes++;
    }
}

void StreamReassembler::assemble() {
    string s;
    while (_unassemble_mask.front()) {
        s.push_back(_unassembles.front());
        _unassembles.pop_front();
        _unassemble_mask.pop_front();
        _unassembles.push_back('\0');
        _unassemble_mask.push_back(false);
    }

    if (s.empty())
        return;

    _output.write(s);
    _next_index += s.size();
    _unassambled_bytes -= s.size();
}

size_t StreamReassembler::unassembled_bytes() const { return _unassambled_bytes; }

bool StreamReassembler::empty() const { return _unassambled_bytes == 0; }

在命令行中输入:

复制cd build
make -j8
make check_lab1

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

调试代码

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

tasks.json

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

复制{
    "tasks": [
        {
            "type": "shell",
            "label": "cmake",
            "command": "cd build && cmake .. -DCMAKE_BUILD_TYPE=Debug",
            "detail": "CMake 生成 Makefile",
            "args": [],
            "problemMatcher": "$gcc"
        },
        {
            "type": "shell",
            "label": "build",
            "command": "cd build && make -j8",
            "detail": "编译项目",
            "args": [],
            "problemMatcher": "$gcc"
        },
    ],
    "version": "2.0.0"
}

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

launch.json

在 .vscode 文件夹中新建 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 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"
        }
    ]
}

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

调试效果如下图所示:

后记

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/888316.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows Server --- RDP远程桌面服务器激活和RD授权

RDP远程桌面服务器激活和RD授权 一、激活服务器二、设置RD授权 系统&#xff1a;Window server 2008 R2 服务&#xff1a;远程桌面服务 注&#xff1a;该方法适合该远程桌面服务器没网络状态下&#xff08;离线&#xff09;&#xff0c;激活服务器。 一、激活服务器 1.打开远…

Spring学习笔记之Bean的循环依赖问题

文章目录 什么是Bean的循环依赖singleton下的set注入产生的循环依赖prototype下的set注入产生的循环依赖singleton下的构造注入产生的循环依赖Spring解决循环循环的机理&#xff08;面试题&#xff09; 什么是Bean的循环依赖 A对象中有B属性。B对象中有A属性。这就是循环依赖。…

leetcode 1614.括号的最大嵌套深度

⭐️ 题目描述 &#x1f31f;leetcode链接&#xff1a;括号的最大嵌套深度 ps&#xff1a; 使用数据结构栈来存储 ( 在使用 maxDepth 变量记录栈顶 top 的最大值&#xff0c;当遇到 ) 时删除栈顶元素。举个例子 (1)((2))(((3)))&#xff0c;当遇到第一个 ( 时 top 1&#xff…

对dubbo的DubboReference.check的参数进行剖析

背景 在使用dubbo的时候&#xff0c;发现当消费者启动的时候&#xff0c;如果提供者没有启动&#xff0c;即使提供者后来启动了&#xff0c;消费者也调不通提供者提供的接口了。 注册中心使用都是nacos dubbo版本是3.0.4 例子 接口 public interface DemoService {String…

中期国际:外汇交易的利器:善用挂单技巧优化交易策略

在外汇交易中&#xff0c;挂单技巧是提高交易效率和灵活性的重要利器之一。善用限价单和止损单可以帮助交易者有效规避风险、控制入场点和出场点&#xff0c;从而提高交易效果。本文将介绍一些MT4挂单技巧&#xff0c;以帮助交易者优化交易策略&#xff0c;提高交易效率。 1. 了…

猿辅导设立“青少年科学探索基金”,鼓励天才少年投入科学研究

“少年智则国智&#xff0c;少年富则国富&#xff0c;少年强则国强。”国家发展离不开人才的培养。伴随我国进入高质量发展轨道&#xff0c;科学、人才、教育三位一体融合发展已经刻不容缓。我国基础学科人才紧缺成了不争的事实。目前&#xff0c;中国的GDP目前已是世界第二位&…

nginx创建和监听套接字分析

https://cloud.tencent.com/developer/article/1859856 简介 nginx作为一个web服务器&#xff0c;肯定是有listen套接字对外提供服务的&#xff0c;listen套接字是用于接收HTTP请求。 nginx监听套接字的创建是根据配置文件的内容来创建的&#xff0c;在nginx.conf文件中有…

视频音乐如何转换成mp3?教你超简单的转换方法

MP3文件通常比视频文件更小。因此&#xff0c;通过将音乐从视频中提取并转换为MP3格式&#xff0c;您可以更轻松地存储和传输它们。如果计划在手机或其他设备上存储音乐&#xff0c;转换为MP3格式可以帮助我们节省存储空间。而且&#xff0c;如果需要将音乐发送给朋友或上传到互…

基于JAVA高校校园点餐系统-lw+ppt

文章目录 前言一、主要技术javaMysql数据库JSP技术 二、系统设计1. 系统结构图 三、功能截图总结 前言 21世纪的今天&#xff0c;随着社会的发展与进步&#xff0c;人们对信息科学的认识已从低层次提升到高层次&#xff0c;从感性认识逐渐转变为理性认识。管理工作的重要性也逐…

新生录取查询系统怎么制作?

在制作新生录取查询系统前&#xff0c;先跟老师们介绍一下招生录取的详细流程&#xff0c;以便老师们更好的完成录取工作的筹备&#xff0c;顺利过渡招生季&#xff01; 1. 招生宣传和报名&#xff1a;学校通过各种途径进行招生宣传&#xff0c;向学生和家长介绍学校的特色、教…

图数据库_Neo4j学习cypher语言_常用函数_关系函数_字符串函数_聚合函数_数据库备份_数据库恢复---Neo4j图数据库工作笔记0008

然后再来看一些常用函数,和字符串函数,这里举个例子,然后其他的 类似 可以看到substring字符串截取函数 可以看到截取成功 聚合函数 这里用了一个count(n) 统计函数,可以看到效果 关系函数,我们用过就是id(r) 可以取出对应的r的id来这样..

北京影视展BIRTV 2023亮点提前盘点

2023年8月23-26日&#xff0c;“融合创新 面向未来”——由国家广播电视总局和中央广播电视总台共同指导&#xff0c;中国广播电视国际经济技术合作总公司主办的第三十届北京国际广播电影电视展览会&#xff08;BIRTV2023&#xff09;将在北京中国国际展览中心&#xff08;朝阳…

大股东被纪检监察调查,会否成为大牧人上市之路的又一拦路虎?

据悉&#xff0c;深圳证券交易所上市审核委员会已经定于2023年8月17日召开2023年第63次上市审核委员会审议会议&#xff0c;审核青岛大牧人机械股份有限公司&#xff08;即“大牧人”&#xff09;首发上市。这已经是大牧人因股东股权纷争&#xff08;见相关媒体报道&#xff09…

项目经理掌控项目进度的重要手段——甘特图

项目经理最担心的是无法了解团队成员每天的工作内容以及项目的进展情况。因此&#xff0c;每天的会议和项目周报是项目经理掌控项目进度的重要手段&#xff0c;能够帮助项目经理及时了解和跟踪项目的进展。 进度控制是指监督项目状态、更新项目进展、管理进度基准变更&#x…

Python Tkinter 树状浏览图,类和函数及文件浏览的应用(由idlelib tree模块修改)

模块由idlelib tree模块修改&#xff0c;完善一些问题&#xff0c;重写了获取类和函数的方法&#xff0c;便于获取正在编辑代码的类和函数。重写了文件浏览模块&#xff0c;支持添加收藏&#xff0c;双击py&#xff08;pyw&#xff09;文件会打开函数浏览器&#xff0c;文件浏览…

【AGC】崩溃数据消失问题

【问题背景】 最近有开发者集成了AGC的崩溃服务&#xff0c;出现了一个问题&#xff0c;在集成完成后&#xff0c;触发崩溃事件测试&#xff0c;在AGC后台可以看到当天崩溃的数据&#xff0c;但是启动次数显示为0。等到第二天再看数据时&#xff0c;连昨天的崩溃数据都没有了。…

一文带你搞懂MySQL的隔离级别

一. 前言 最近遇到这样一个题目&#xff1a;【假设目前你们使用的数据库是MySQL&#xff0c;现在有一个事务A&#xff0c;在事务A开始时读取数据的结果是1&#xff1b;事务A中间有一段耗时操作&#xff0c;在事务A中做耗时操作的同时&#xff0c;有另外一个事务B把数据值改成了…

MFC为控件添加背景图片

1、 添加选择Bitmap导入图片&#xff0c;图片文件最好放在项目res目录中&#xff0c;同时是BMP格式。上传后的图片在资源视图&#xff0c;命名为IDB_BITMAP_M_BACK。 2、在cpp的C***Dlg::OnPaint()函数下添加如下代码 void C***Dlg::OnPaint() {CPaintDC dc(this); // device…

录制游戏视频的软件有哪些?分享3款软件!

“有录制游戏视频的软件推荐吗&#xff1f;最近迷上了网游&#xff0c;想录制点自己高端操作的游戏画面&#xff0c;但是不知道用什么软件录屏比较好&#xff0c;就想问问大家&#xff0c;有没有好用的录制游戏视频软件。” 在游戏领域&#xff0c;玩家们喜欢通过录制游戏视频…

DevExpress WinForms数据编辑器组件,提供丰富的数据输入样式!(一)

DevExpress WinForms超过80个高影响力的WinForms编辑器和多用途控件&#xff0c;从屏蔽数据输入和内置数据验证到HTML格式化&#xff0c;DevExpress数据编辑库提供了无与伦比的数据编辑选项&#xff0c;包括用于独立数据编辑或用于容器控件(如Grid, TreeList和Ribbon)的单元格。…