【斯坦福计网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_;
}

相关文章
|
8天前
【科研指南8】如何快速批量下载一篇论文后的所有的参考文献?附赠Endnote分组论文管理
【科研指南8】如何快速批量下载一篇论文后的所有的参考文献?附赠Endnote分组论文管理
113 0
|
8天前
|
网络协议 索引
【斯坦福计网CS144项目】Lab2 实现一个简单的 TCP 接收类
【斯坦福计网CS144项目】Lab2 实现一个简单的 TCP 接收类
5 0
|
8天前
|
机器学习/深度学习 语音技术 数据库
ICLR 2024:为音视频分离提供新视角,清华大学胡晓林团队推出RTFS-Net
【2月更文挑战第17天】ICLR 2024:为音视频分离提供新视角,清华大学胡晓林团队推出RTFS-Net
35 1
ICLR 2024:为音视频分离提供新视角,清华大学胡晓林团队推出RTFS-Net
|
8天前
|
网络协议 开发工具 网络架构
【斯坦福计网CS144】Lab2终结笔记
【斯坦福计网CS144】Lab2终结笔记
40 0
|
8天前
|
缓存 网络协议 开发工具
【斯坦福计网CS144】Lab1终结笔记
【斯坦福计网CS144】Lab1终结笔记
67 0
|
8天前
|
安全 网络协议 网络安全
【斯坦福计网CS144】Lab6终结笔记
【斯坦福计网CS144】Lab6终结笔记
44 0
|
8天前
|
网络协议 开发工具 git
【斯坦福计网CS144】Lab4终结笔记
【斯坦福计网CS144】Lab4终结笔记
25 0
|
8天前
|
网络协议 Linux 网络性能优化
【斯坦福计网CS144】Lab5终结笔记
【斯坦福计网CS144】Lab5终结笔记
38 0
|
8天前
|
网络协议 安全 网络安全
【斯坦福计网CS144】Lab7终结笔记
【斯坦福计网CS144】Lab7终结笔记
48 0
|
8天前
|
存储 网络协议 算法
【斯坦福计网CS144】Lab3终结笔记
【斯坦福计网CS144】Lab3终结笔记
22 0