CS144(2023 Spring)Lab 1: stitching substrings into a byte stream

news2025/4/6 6:13:47

文章目录

  • 前言
    • 其他笔记
    • 相关链接
  • 1. Getting started
  • 2. Putting substrings in sequence
    • 2.1 需求分析
    • 2.2 注意事项
    • 2.3 代码实现
  • 3. 测试与优化

前言

这一个Lab主要是实现一个TCP receiver的字符串接收重组部分。

其他笔记

Lab 0: networking warmup
Lab 1: stitching substrings into a byte stream

相关链接

课程主页
lab 1

1. Getting started

CS144这个Lab本来是自上而下从头到尾代码复用的,这导致我开局顺手就把远程库给干掉了,结果没想到到Lab居然叫我merge,那就重新弄一下吧:

git remote add base  git@github.com:CS144/minnow.git
git fetch base

打开VS的分支管理器,可以看到这个远程库里面应该是check1 - 6挨个发布的,看提交记录这个应该是当时上课的时候才一步步弄的仓库,理论上来讲直接合并check6的分支就可以了
在这里插入图片描述
右键合并到main,接受冲突,然后提交上传,就可以继续撸码了
在这里插入图片描述

2. Putting substrings in sequence

2.1 需求分析

在这里插入图片描述
Lab1和2做的事情是写一个TCP接收器,大概工作就如同Lab0的末尾写的那样,写一个类去处理字节流,不过这个数据将不用内存传输上,而是通过网络传输。

由于网络传输的不确定性以及成本问题,在传输数据时我们都是将串切成一段一段的,比如这里提到的每个 s h o r t   s e g m e n t s short\ segments short segments不超过1460个 b y t e byte byte,又是考虑到网络传输的不确定性以及TCP的性质,这些字段通常会出现乱序、丢失的情况,而我们需要保证能够重排回最初的字符串。

具体到本Lab,我们要实现一个叫 R e a s s e m b l e r Reassembler Reassembler的东西,这是用来在接收端接受上面说的那一堆字段的,而每一个 B y t e Byte Byte(而非 s e g m e n t segment segment)都有一个对应的 i n d e x index index。文档约束了这个类的两个必要接口,insert将一个data写入output,写入的位置自first_index起,它还用了一个bool变量去标识当前段是否为最后一个段;而bytes_pending则仅仅返回一下存在 R e a s s e m b l e r Reassembler Reassembler中的字节,但是哪些字节存在这里面呢?我们知道单纯网络传输不保证顺序,有可能提前接收到了后面的字段,就只好暂存在 R e a s s e m b l e r Reassembler Reassembler,等它前面的字段写完了再存进去。

然后进一步展示了这个类应当做的工作的一些细节。
在这里插入图片描述
首先,我们应当知道流的下一个待接收的字节(的 i n d e x index index),正如上面说的那样,类内部还有一大堆字段嗷嗷待哺等着进流;

在这里插入图片描述
然后,我们需要处理提前到达的暂时没被推进流的串;

在这里插入图片描述
而对于哪些超出流接收能力的字节,应当直接扔掉;

在这里插入图片描述
然后这个图演示了总共存在三类byte:未进流暂存的、已进流缓存的、已被read弹出的,第三个我们这个Lab应该不用考虑。绿色内存以及类内暂存的一整块空间共同组成了capacity,可知我们的红色内存的最大值只能为capacity - buffered,超过这个的字节就得丢掉了。在实现上,这个值就是上一个Lab实现的available_capacity

2.2 注意事项

在这里插入图片描述
然后是一些FAQ,我们可以提炼出这些信息:

  1. 流的 i n d e x index index自0始;
  2. 我们会同Lab0一样有个跑分环境;
  3. 每个字段都是来自字符串的准确切片,不用做异常处理;
  4. 鼓励用标准库、数据结构;
  5. 尽可能早地将字节推进流,免得一直存着;
  6. insert接收到的data字符串是有可能与其他字符串重叠的;
  7. 可以往类里面加私有成员(这不是废话吗);
  8. 对于每一个字节,类内部应当只存储它的一份副本,不要存重叠的字符串;
  9. 运行./scripts/lines-of-code以计算实现代码行数,这个值一般在50-60。

2.3 代码实现

下面给出我的代码实现,里面有很多注释,就不挨着说了,不过注意我这里用到了std::rangesstd::views,因此你的编译器要在gcc13.1及以上。稍微需要探讨一下的是用什么数据结构来当做 b u f f e r buffer buffer,这个数据结构需要满足什么样的需求呢?首先它会有频繁的任意处插入,然后它需要去频繁遍历比较大小查找,给出几个常见的数据结构的复杂度:

插入头删删除k个查找
list O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1) O ( k ) O(k) O(k) O ( n ) O(n) O(n)
vector O ( n ) O(n) O(n) O ( n ) O(n) O(n) O ( n ) O(n) O(n) O ( log ⁡ n ) O(\log n) O(logn)
deque O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( log ⁡ n ) O(\log n) O(logn)
map O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n + k ) O(\log n + k) O(logn+k) O ( n ) O(n) O(n)

可以看到,综合考虑下list基本是最优秀的容器了。其中虽然map红黑树自带的查找是 O ( log ⁡ n ) O(\log n) O(logn),但是我们的查找是要查找两个端点,如果将左右区间的pair作为key的话就不能用它内置的二分查找算法——它无法传递自定义比较谓词,而使用<algorithm>中的二分算法的话又因为它的迭代器不满足随即迭代器的条件,意味着只能 O ( n ) O(n) O(n)查找。综合来看,我们维护一个有序链表是最优的。

此外在向 b u f f e r buffer buffer暂存的过程中,可能涉及到区间合并的问题,可以参考LeetCode 57. 插入区间
,给出这道题我的实现,本Lab直接套用即可:

// https://leetcode.cn/u/zi-bu-yu-mf/
class Solution {
public:
    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
        auto beg = intervals.begin(), end = intervals.end();
        int& a = newInterval[0], & b = newInterval[1];
        auto l = lower_bound(beg, end, vector{ a, a });
        auto r = upper_bound(  l, end, vector{ b, b});
        if (l != end) a = min(a, l[ 0][0]);
        if (r != beg) b = max(b, r[-1][1]);
        intervals.insert(intervals.erase(l, r), newInterval);
        return intervals;
    }
};
/*****************************************************************//**
 * \file   reassembler.hh
 * \brief  实现一个 Reassembler 类, 用于将乱序的字符串重新组装成有序的
 *         字符串,并推入字节流.
 * 
 * \author JMC
 * \date   August 2023
 *********************************************************************/
#pragma once

#include "byte_stream.hh"

#include <string>
#include <list>
#include <tuple>

class Reassembler
{
	bool had_last_ {};	// 是否已经插入了最后一个字符串
	uint64_t next_index_ {};	// 下一个要写入的字节的索引
	uint64_t buffer_size_ {};	// buffer_中的字节数
	std::list<std::tuple<uint64_t, uint64_t, std::string>> buffer_ {};

	/**
	 * \breif 将data推入output流.
	 */
	void push_to_output(std::string data, Writer& output);

	/**
	 * \brief 将data推入buffer暂存区.
	 * \param first_index data的第一个字节的索引
	 * \param last_index  data的最后一个字节的索引
	 * \param data        待推入的字符串, 下标为[first_index, last_index]闭区间
	 */
	void buffer_push( uint64_t first_index, uint64_t last_index, std::string data );

	/**
	 * 尝试将buffer中的串推入output流.
	 */
	void buffer_pop(Writer& output);

public:
  /*
   * Insert a new substring to be reassembled into a ByteStream.
   *   `first_index`: the index of the first byte of the substring
   *   `data`: the substring itself
   *   `is_last_substring`: this substring represents the end of the stream
   *   `output`: a mutable reference to the Writer
   *
   * The Reassembler's job is to reassemble the indexed substrings (possibly out-of-order
   * and possibly overlapping) back into the original ByteStream. As soon as the Reassembler
   * learns the next byte in the stream, it should write it to the output.
   *
   * If the Reassembler learns about bytes that fit within the stream's available capacity
   * but can't yet be written (because earlier bytes remain unknown), it should store them
   * internally until the gaps are filled in.
   *
   * The Reassembler should discard any bytes that lie beyond the stream's available capacity
   * (i.e., bytes that couldn't be written even if earlier gaps get filled in).
   *
   * The Reassembler should close the stream after writing the last byte.
   */
  void insert( uint64_t first_index, std::string data, bool is_last_substring, Writer& output );

  // How many bytes are stored in the Reassembler itself?
  uint64_t bytes_pending() const;
};
/*****************************************************************//**
 * \file   reassembler.cc
 * \brief  实现一个 Reassembler 类, 用于将乱序的字符串重新组装成有序的
 *         字符串,并推入字节流.
 * \author JMC
 * \date   August 2023
 *********************************************************************/
#include "reassembler.hh"

#include <ranges>
#include <algorithm>

using namespace std;
void Reassembler::push_to_output( std::string data, Writer& output ) {
  next_index_ += data.size();
  output.push( move( data ) );
}

void Reassembler::buffer_push( uint64_t first_index, uint64_t last_index, std::string data )
{
  // 合并区间
  auto l = first_index, r = last_index;
  auto beg = buffer_.begin(), end = buffer_.end();
  auto lef = lower_bound( beg, end, l, []( auto& a, auto& b ) { return get<1>( a ) < b; } );
  auto rig = upper_bound( lef, end, r, []( auto& b, auto& a ) { return get<0>( a ) > b; } );
  if (lef != end) l = min( l, get<0>( *lef ) );
  if (rig != beg) r = max( r, get<1>( *prev( rig ) ) );
  
  // 当data已在buffer_中时,直接返回
  if ( lef != end && get<0>( *lef ) == l && get<1>( *lef ) == r ) {
    return;
  }

  buffer_size_ += 1 + r - l;
  if ( data.size() == r - l + 1 && lef == rig ) { // 当buffer_中没有data重叠的部分
	buffer_.emplace( rig, l, r, move( data ) );
	return;
  }
  string s( 1 + r - l, 0 );

  for ( auto&& it : views::iota( lef, rig ) ) {
	auto& [a, b, c] = *it;
	buffer_size_ -= c.size();
    ranges::copy(c, s.begin() + a - l);
  }
  ranges::copy(data, s.begin() + first_index - l);
  buffer_.emplace( buffer_.erase( lef, rig ), l, r, move( s ) );
}

void Reassembler::buffer_pop( Writer& output ) {
  while ( !buffer_.empty() && get<0>( buffer_.front() ) == next_index_ ) {
    auto& [a, b, c] = buffer_.front();
    buffer_size_ -= c.size();
    push_to_output( move( c ), output ); 
    buffer_.pop_front();
  }

  if ( had_last_ && buffer_.empty() ) {
    output.close();
  }
}

void Reassembler::insert( uint64_t first_index, string data, bool is_last_substring, Writer& output )
{
  if ( data.empty() ) {
    if ( is_last_substring ) {
      output.close();
    }
    return;
  }
  auto end_index = first_index + data.size();                  // data: [first_index, end_index)
  auto last_index = next_index_ + output.available_capacity(); // 可用范围: [next_index_, last_index)
  if ( end_index < next_index_ || first_index >= last_index ) {
    return; // 不在可用范围内, 直接返回
  }

  // 调整data的范围
  if ( last_index < end_index ) {
    end_index = last_index;
    data.resize( end_index - first_index );
    is_last_substring = false;
  }
  if ( first_index < next_index_ ) {
    data = data.substr( next_index_ - first_index );
    first_index = next_index_;
  }

  // 若data可以直接写入output, 则直接写入
  if ( first_index == next_index_ && ( buffer_.empty() || end_index < get<1>( buffer_.front() ) + 2 ) ) {
    if ( buffer_.size() ) { // 若重叠, 则调整data的范围
      data.resize( min( end_index, get<0>( buffer_.front() ) ) - first_index );
    }
    push_to_output( move( data ), output );
  } else { // 否则, 将data插入buffer_
    buffer_push( first_index, end_index - 1, data );
  }
  had_last_ |= is_last_substring;
  
  // 尝试将buffer_中的数据写入output
  buffer_pop(output);
}

uint64_t Reassembler::bytes_pending() const
{
  return buffer_size_;
}

3. 测试与优化

写这个Lab我跑测试遇到了挺多Bug的,心态比较麻,看一下我的git记录,大体代码从五点到六点半就写完了,然后又调了三四个小时的Bug。。。:
在这里插入图片描述

在这里插入图片描述

这个Lab最主要的优化点还是要记得使用std::move转发字符串,然后这是我不管字符串能不能直接进流,都先进buffer后进流的速度
在这里插入图片描述
这是我对这种情况的特殊处理后的速度,可以看到来到了9.5Gbit/s+8Gbit/s这个速度,按理说我的入buffer操作对于这种情况仅仅是多跑了一遍链表而已,没想到速度也差不少:
在这里插入图片描述
最后看看代码行数,运行./scripts/lines-of-code,如果报错则需要安装一个工具sudo apt-get install sloccount
在这里插入图片描述
它说基础代码是22行,我们就写了77行。我看了一下这个统计行数其实就是去除了注释和空行,他说50-60行是正常的,我这里写得比较详细,想压行也不是压不了,也差不多。
在这里插入图片描述

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

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

相关文章

【Datawhale】AI夏令营第三期——基于论文摘要的文本分类笔记(上)

暑期参加了Datawhale的第三期AI夏令营&#xff0c;学习的是NLP方向&#xff0c;在此期间&#xff0c;我们通过比赛打榜的形式进行NLP的学习。今天&#xff0c;主要分享和记录一下这一期夏令营的学习历程和笔记。 文章目录 赛题背景赛题任务赛题数据集评价指标解题思路任务一&am…

C++之“00000001“和“\x00\x00\x00\x01“用法区别(一百八十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

NTP时钟同步服务器

目录 一、什么是NTP&#xff1f; 二、计算机时间分类 三、NTP如何工作&#xff1f; 四、NTP时钟同步方式&#xff08;linux&#xff09; 五、时间同步实现软件&#xff08;既是客户端软件也是服务端软件&#xff09; 六、chrony时钟同步软件介绍 七、/etc/chrony.conf配置文件介…

75 # koa 基本逻辑实现以及属性的扩展

准备工作 新建自己的 kaimo-koa 文件夹&#xff0c;结构如下&#xff1a; lib application.js&#xff1a;创建应用context.js&#xff1a;上下文request.js&#xff1a;koa 中自己实现的 request 的对象response.js&#xff1a;koa 中自己实现的 response 的对象 package.js…

HTTPS安全通信和SSL Pinning

随着互联网的迅速发展&#xff0c;网络通信安全问题日益凸显。在这一背景下&#xff0c;HTTPS作为一种加密通信协议得到了广泛应用&#xff0c;以保障用户的数据隐私和信息安全。本文将介绍HTTPS的基本原理、发展历程&#xff0c;以及与之相关的中间人攻击和防护方法。 1. HTT…

MySQL基础入门

推荐查看 数据库相关概念 MySQL百度百科 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Databa…

爬虫逆向实战(二十八)--某税网第一步登录

一、数据接口分析 主页地址&#xff1a;某税网 1、抓包 通过抓包可以发现登录接口是factorAccountLogin 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看载荷模块可以发现有一个datagram 和 一个signature加密参数 请求头是否加密&#xff1f; 通过查看“标…

【Flutter】下载安装Flutter并使用学习dart语言

前言 安装flutter, 并使用flutter内置的dartSDK学习使用dart语言。 编辑器&#xff1a; Android Studio fluuter 版本 : flutter_windows_3.13.1 内置dartSDK : 3.1.0 dart路径路径&#xff1a; flutter安装路径\bin\cache\dart-sdk 安装Flutter 下载安装包 flutter下载地址…

QT 界面相关操作

1> 创建自定义类时需要指定父类 2> 第一个界面的相关操作 #include "widget.h" #include<iostream> //printf #include<QDebug> //qDebuf #include<QIcon> //图标的头文件 using namespace std; //coutWidget::Widget(QWidget *…

【注册岩土】Python土力学与基础工程计算.PDF-摩尔-库伦强度理论

8.3 Python求解 Python求解代码如下&#xff1a; 1.import math 2. 3.sigma1 300 # 最大主应力&#xff0c;单位 kPa 4.sigma3 100 # 最小主应力&#xff0c;单位 kPa 5.alpha 30 # m-n面与最小主应力方向夹角&#xff0c;单位度 6. 7.rad_alph…

uniapp的 picker 日期时间选择器

效果图&#xff1a; dateTimePicker.js function withData(param){return param < 10 ? 0 param : param; } function getLoopArray(start,end){var start start || 0;var end end || 1;var array [];for (var i start; i < end; i) {array.push(withData(i))…

C++网狐服务器引入开源日志库

很多人对日志库不以为然&#xff0c;包括网狐这种十几年的公司都不重视&#xff0c;其实日志库记录的东西能在线上出问题时高效解决&#xff0c;特别是别人写的东西&#xff0c;人又走了&#xff0c;出了问题&#xff0c;还可以用日志分析快速解决。要是没有日志记录&#xff0…

【kubernetes系列】Calico原理及配置

概述 Calico是针对容器&#xff0c;虚拟机和基于主机的本机工作负载的开源网络和网络安全解决方案。 Calico支持广泛的平台&#xff0c;包括Kubernetes&#xff0c;OpenShift&#xff0c;Docker EE&#xff0c;OpenStack和裸机服务。 Calico在每个计算节点都利用Linux Kernel实…

TiDB Serverless Branching:通过数据库分支简化应用开发流程

2023 年 7 月 10 日&#xff0c;TiDB Serverless 正式商用。这是一个完全托管的数据库服务平台&#xff08;DBaaS&#xff09;&#xff0c;提供灵活的集群配置和基于用量的付费模式。紧随其后&#xff0c;TiDB Serverless Branching 的测试版也发布了。 TiDB Serverless Branc…

【二等奖方案】大规模金融图数据中异常风险行为模式挖掘赛题「冀科数字」解题思路

第十届CCF大数据与计算智能大赛&#xff08;2022 CCF BDCI&#xff09;已圆满结束&#xff0c;大赛官方竞赛平台DataFountain&#xff08;简称DF平台&#xff09;正在陆续释出各赛题获奖队伍的方案思路&#xff0c;欢迎广大数据科学家交流讨论。 本方案为【大规模金融图数据中…

Java代码审计15之Apache log4j2漏洞

文章目录 1、log4j简介2、复现2.1、高版本测试2.2、测试代码2.3、补充之dns探测2.3.1、rmi、ldap也可以dnslog探测 2.3.2、dnslog外带信息 3、漏洞原理3.1、漏洞的危害大的背景3.2、具体的代码调试 4、靶场测试4.1、dns探测4.2、工具下载与使用4.3、测试 5、bypass 1、log4j简介…

FBX SDK 开发环境配置 visual studio 2022

FBX | Adaptable File Formats for 3D Animation Software | Autodesk. 下载windows的sdk并安装. 创建一个c console 工程 设置include目录 添加预处理宏 FBX_SHARED1 添加fbx sdk lib 目录 添加依赖lib : libfbxsdk-md.lib libxml2-md.lib zlib-md.lib 配置完毕.

C# | DBSCAN聚类算法实现 —— 对直角坐标系中临近点的点进行聚类

C# | DBSCAN聚类算法实现 聚类算法是一种常见的数据分析技术&#xff0c;用于将相似的数据对象归类到同一组或簇中。其中&#xff0c;DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的聚类算法&#xff0c;能够有效…

【算法训练-数组 三】数组中的第K个最大元素(TOPK问题|寻找第K大)

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【寻找第K大】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…

第 3 章 栈和队列(顺序栈,算法 3.3)

1. 背景说明&#xff1a; 若迷宫 maze 中存在从入口 start 到出口 end 的通道&#xff0c;则求得一条存放在栈中(从栈底到栈顶)&#xff0c;并返回 TRUE&#xff1b;否则返回 FALSE&#xff0c;注意&#xff0c;该解并非最优解&#xff0c; 最优解需要求得最短路径且可能并非…