【斯坦福计网CS144项目】Lab1 实现一个流重组器

简介: 【斯坦福计网CS144项目】Lab1 实现一个流重组器

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

一、实验目的

  1. 实现一个流重组器——一个将字节流的小块 (称为子串或段 )按正确顺序组装成连续的字节流的模块
  2. 深入理解 TCP 协议的工作方式

二、实验说明

在lab0中,我们使用了Linux操作系统内置的传输控制协议(TCP)实现,并利用互联网流套接字从一个网站获取信息并发送电子邮件。尽管底层网络只提供"尽力而为"的数据报服务,但我们成功地通过在计算机内存中自行实现字节流的抽象来生成了一对可靠的按顺序排列的字节流,其中一个字节流是从主机到服务器的,另一个字节流是从服务器到主机的。

  1. 在lab1要求编写一个名为"StreamReassembler"的数据结构,它负责重新组装数据。该结构将接收子串(由一串字节和大数据流中该串的第一个字节的索引组成),并提供一个名为"ByteStream"的输出,其中所有的数据都被正确排序。

三、实验内容

  1. 输入"cd minnow/build"切换到实验作业的存储库中,运行”git fetch”来检索实验作业的最新版本。通过运行"git merge origin/check1-startercode",下载lab 1 的启动代码。如图 2-1 所示。

图 2-1 获取实验代码

注意:如果拉取合并代码后报错,可以先把之前的代码单独拷贝出来,然后从lab0那个网站拉取代码,直接合并lab1的代码,再把自己的代码拷贝进去即可。

  1. 在文件"./src/reassembler.hh ./src/reassembler.cc"中代码修改如图2-2所示。代码源码见附录。

图2-2 reassembler.hh 代码细节

图2-3 reassembler.cc 代码细节

  1. 确认文件无误后,在/build目录下输入 "make check1"对文件进行编译测试,测试结果如图2-4所示。可以看到所有的测试结果全部通过。

图2-4 测试结果

整体实现框架如图2-5所示。

图2-5 整体框架

四、实验体会

在这个实验中,我们的目的是实现一个流重组器,它能够将字节流的小块按正确顺序组装成连续的字节流。通过这个实验,我们不仅要达到技术上的理解和掌握,还要深入思考TCP协议的工作方式。

首先,在实验说明中,我们了解到在lab0中,使用了Linux操作系统内置的传输控制协议(TCP)实现,并利用互联网流套接字从一个网站获取信息并发送电子邮件。尽管网络只提供"尽力而为"的数据报服务,但通过在计算机内存中自行实现字节流的抽象,我们成功地生成了一对可靠的按顺序排列的字节流。这让我们对TCP协议的工作方式有了更深刻的理解。

接着,在实验内容中,我们需要编写一个名为"StreamReassembler"的数据结构,用于重新组装数据。这个数据结构接收子串,其中子串由一串字节和大数据流中该串的第一个字节的索引组成。我们需要确保输出的"ByteStream"中的数据按照正确的顺序进行排序。

为了完成这个实验,我们需要进行一系列步骤。首先,我们切换到实验作业的存储库中,并运行"git fetch"来检索实验作业的最新版本。然后,我们运行"git merge origin/check1-startercode"来下载lab 1的启动代码。在修改了相关代码后,我们在/build目录下进行编译测试,使用"make check1"命令。通过测试结果可以看到,所有的测试都通过了,证明我们的实现是正确的。

通过这个实验,我们得出了一些体会和总结。在理解性方面,我们深入了解了TCP协议的工作原理,包括连接建立、数据传输和连接关闭等过程。在总结性方面,我们意识到了网络编程的重要性,并学习了处理缓存数据、接收数据和组装字节流等实际开发中需要注意的问题。在知识性方面,我们掌握了TCP协议的相关知识和流重组器的实现方法。

综合来看,这个实验对于我们的学习和成长具有重要意义。通过实践,我们不仅掌握了网络编程和计算机科学的相关知识和技能,还提高了我们的实际开发能力。在今后的学习和工作中,我们可以更加熟练地处理网络数据和解决相关问题,为我们未来的职业发展打下坚实的基础。

五、代码附录

  1. 附录一:reassembler.hh
#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_ {};

  /**
   * \brief 将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;
};
  1. 附录二:reassembler.cc
#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_;
}

相关文章
|
机器学习/深度学习 人工智能 自然语言处理
一文尽览 | 开放世界目标检测的近期工作及简析!(基于Captioning/CLIP/伪标签/Prompt)(上)
人类通过自然监督,即探索视觉世界和倾听他人描述情况,学会了毫不费力地识别和定位物体。我们人类对视觉模式的终身学习,并将其与口语词汇联系起来,从而形成了丰富的视觉和语义词汇,不仅可以用于检测物体,还可以用于其他任务,如描述物体和推理其属性和可见性。人类的这种学习模式为我们实现开放世界的目标检测提供了一个可以学习的角度。
一文尽览 | 开放世界目标检测的近期工作及简析!(基于Captioning/CLIP/伪标签/Prompt)(上)
|
3月前
|
数据采集 存储 开发者
构建你的第一个Python网络爬虫:从理论到实践
【8月更文挑战第31天】在数字时代的浪潮中,数据成为了新的石油。本文将引导初学者通过Python编程语言搭建一个基础的网络爬虫,从互联网的海洋中提取有价值的信息。文章不仅会介绍网络爬虫的工作原理和应用场景,还会通过实际代码示例展示如何实现一个简单的爬虫项目。无论你是编程新手还是有一定基础的开发者,都能通过这篇文章获得宝贵的实践经验和技术洞见。
|
3月前
|
安全 Java Go
最新进展:Go arena 手动管理内存,鸽了!
最新进展:Go arena 手动管理内存,鸽了!
|
6月前
|
网络协议 索引
【斯坦福计网CS144项目】Lab2 实现一个简单的 TCP 接收类
【斯坦福计网CS144项目】Lab2 实现一个简单的 TCP 接收类
43 0
|
6月前
|
网络协议 开发工具 git
【斯坦福计网CS144】Lab4终结笔记
【斯坦福计网CS144】Lab4终结笔记
93 0
|
6月前
|
存储 网络协议 算法
【斯坦福计网CS144】Lab3终结笔记
【斯坦福计网CS144】Lab3终结笔记
57 0
|
6月前
|
网络协议 开发工具 网络架构
【斯坦福计网CS144】Lab2终结笔记
【斯坦福计网CS144】Lab2终结笔记
70 0
|
6月前
|
缓存 网络协议 开发工具
【斯坦福计网CS144】Lab1终结笔记
【斯坦福计网CS144】Lab1终结笔记
131 0
|
6月前
|
网络协议 安全 网络安全
【斯坦福计网CS144】Lab7终结笔记
【斯坦福计网CS144】Lab7终结笔记
76 0
|
6月前
|
安全 网络协议 网络安全
【斯坦福计网CS144】Lab6终结笔记
【斯坦福计网CS144】Lab6终结笔记
78 0