当前位置: 首页 > news >正文

2025 cs144 Lab Checkpoint 1小白超详细版

cs144官网:https://cs144.github.io/
我的github:https://github.com/Albert-tru/cs144-2025

文章目录

  • 1 手动发送internet数据报
    • 协议号5、7?
    • 思路?
  • 2 实现一个Reassembler类
    • 2.1 几个帮助理解代码的Q&A
      • Q1:insert的参数data是完整的数据流还是一个数据片段?
      • Q2:insert函数里都需要处理哪些 情况?
      • Q3:既然insert只插入一个数据片段,那我是不是还需要一些成员变量?
      • Q4:如何表示data的写入、抛弃?
    • 其他(调试技巧)
      • 添加标准输出语句来调试的话运行cmake --build --target终端不输出
    • 最终代码

1 手动发送internet数据报

在这里插入图片描述

协议号5、7?

在互联网协议(IP)中,不同的协议类型被分配了不同的数字代码,这些代码用于标识数据包使用的协议类型。

  1. 协议号 5:这通常指的是Internet控制消息协议(ICMP)。ICMP用于在IP主机、路由器之间传递控制消息。它被用于诊断网络问题,例如,当你尝试访问一个不可达的主机时,可能会收到一个ICMP消息(例如,ping请求和响应)。
  2. 协议号 17:这指的是用户数据报协议(UDP)。UDP是一个简单的、无连接的传输层协议,它在网络中提供数据包的传输服务。UDP不保证数据包的顺序、完整性或可靠性,但它提供了快速的数据传输。它常用于那些对速度要求高而对数据完整性要求不高的应用,例如视频流、在线游戏等。

思路?

#include "socket.hh"

using namespace std;

class RawSocket : public DatagramSocket
{
public:
  RawSocket() : DatagramSocket( AF_INET, SOCK_RAW, IPPROTO_RAW ) {}
};

int main()
{
  // construct an Internet or user datagram here, and send using the RawSocket as in the Jan. 10 lecture

  return 0;
}

代码创建了RawSocket,继承自DatagramSocket
要求我们实现数据报的发送,也就是说RawSocket一定会需要包含SendRawSocket函数来发送数据报,并且需要在主函数里面创建一个完整的数据报并调用RawSocket的方法发送它。
这里以发送一个UDP数据报为例。

#include "socket.hh"

using namespace std;

class RawSocket : public DatagramSocket
{
public:
  RawSocket() : DatagramSocket( AF_INET, SOCK_RAW, IPPROTO_UDP) {}
  //发送UDP数据报,需要包含发送的套接字、IP地址、端口号、发送的消息
  void sendUDPPacket(const Address& ip, const string& message)
  {
    sendto(ip,message);
  }
};

int main()
{
  // construct an Internet or user datagram here, and send using the RawSocket as in the Jan. 10 lecture
  RawSocket socket;
  Address add ("127.0.0.1",12345);
  string message = "Hello UDP!";
  socket.sendUDPPacket(add, message);
  return 0;
}

2 实现一个Reassembler类

作用:将接收到的字符串按顺序重新组装。可以理解成有一定量的字符串进来,然后我需要把它排序,

#pragma once

#include "byte_stream.hh"

class Reassembler
{
public:
  // Construct Reassembler to write into given ByteStream.
 //Reassembler的构造函数:
 //explicit:防止隐式转换,必须明确使用构造函数
 //move:移动而不是拷贝,减少资源占用
  explicit Reassembler( ByteStream&& output ) : output_( std::move( output ) ) {}

//要实现的函数
//first_index:表示这段数据在完整数据流中的起始位置
//data:实际要插入的数据
//is_last_substring:是否是最后一个数据片段
  void insert( uint64_t first_index, std::string data, bool is_last_substring );

  // How many bytes are stored in the Reassembler itself?
  // This function is for testing only; don't add extra state to support it.
  uint64_t count_bytes_pending() const;

  // Access output stream reader
  Reader& reader() { return output_.reader(); }
  const Reader& reader() const { return output_.reader(); }

  // Access output stream writer, but const-only (can't write from outside)
  const Writer& writer() const { return output_.writer(); }

private:
  ByteStream output_;
};

在这里插入图片描述

2.1 几个帮助理解代码的Q&A

Q1:insert的参数data是完整的数据流还是一个数据片段?

数据片段。data其实就是一个substring,insert函数的作用是把乱序到达的数据片段插入到完整的数据流中,完整重新组装。因此first_index也不是这个数据流中第一个数据片段的索引位置,而是当前要插入的数据片段的索引。

Q2:insert函数里都需要处理哪些 情况?

其实文档里都写好了:

Q3:既然insert只插入一个数据片段,那我是不是还需要一些成员变量?

  • 记录当前索引位置的变量:now_index,初值为0,每pop出新的字节,需要更新
  • 缓冲区:buffer,如果data的索引没紧挨着当前的索引,就把它放到buffer里
    • 思考一下buffer用啥数据结构?
    • 最好是可以根据索引自动排序的。——map
  • 缓冲区容量(这个数值从哪里来?)
    • 重组器缓冲区的容量和字节流的容量相关。因为重组器最终要写入字节流。
  • 剩余可用容量:用于判断是否丢弃字节

Q4:如何表示data的写入、抛弃?

首先,观察给出的构造函数,包含ByteStream&& output,也就是说Reassembler的输入是ByteStream类型的,所以输出也是ByteStream类型的,所以来看一下ByteStream.hh,(check1里面写的):

#pragma once

#include <cstdint>
#include <string>
#include <string_view>

class Reader;
class Writer;

class ByteStream
{
public:
  explicit ByteStream( uint64_t capacity );

  // Helper functions (provided) to access the ByteStream's Reader and Writer interfaces
  Reader& reader();
  const Reader& reader() const;
  Writer& writer();
  const Writer& writer() const;

  void set_error() { error_ = true; };       // Signal that the stream suffered an error.
  bool has_error() const { return error_; }; // Has the stream had an error?

protected:
  // Please add any additional state to the ByteStream here, and not to the Writer and Reader interfaces.
  uint64_t capacity_;
  bool error_ {};

  //初始化时加上{}是值初始化
  std::string buffer_ {};
  uint64_t bytes_pushed_ {};
  uint64_t bytes_popped_ {};
  bool is_closed_ {};
};

class Writer : public ByteStream
{
public:
  void push( std::string data ); // Push data to stream, but only as much as available capacity allows.
  void close();                  // Signal that the stream has reached its ending. Nothing more will be written.

  bool is_closed() const;              // Has the stream been closed?
  uint64_t available_capacity() const; // How many bytes can be pushed to the stream right now?
  uint64_t bytes_pushed() const;       // Total number of bytes cumulatively pushed to the stream
};

class Reader : public ByteStream
{
public:
  std::string_view peek() const; // Peek at the next bytes in the buffer
  void pop( uint64_t len );      // Remove `len` bytes from the buffer

  bool is_finished() const;        // Is the stream finished (closed and fully popped)?
  uint64_t bytes_buffered() const; // Number of bytes currently buffered (pushed and not popped)
  uint64_t bytes_popped() const;   // Total number of bytes cumulatively popped from stream
};

/*
 * read: A (provided) helper function thats peeks and pops up to `max_len` bytes
 * from a ByteStream Reader into a string;
 */
void read( Reader& reader, uint64_t max_len, std::string& out );

写入:output_.writer.push
抛弃:直接return嘛还是说可以不管超过边界的字节,【我试了试都可以pass】

其他(调试技巧)

一定要ctrl+s再cmake啊!

添加标准输出语句来调试的话运行cmake --build --target终端不输出

那么如何调试呢?
遇到了这个报错
在这里插入图片描述
使用ctest的详细输出模式:

cd build
ctest -V -R reassembler_cap

最终代码

借鉴了这位佬的https://www.zhihu.com/column/c_1877820428258516992
把他的改成了map键值对来存储,中间遇到报错

10: The test "insert last beyond capacity" failed after these steps:
10: 
10:   0.        Initialized Reassembler with capacity=2
10:   1.        Reassembler action: insert "bc" @ index 1 [last substring]
10:   2.        Writer expectation: bytes_pushed = 0
10:   3.        Reassembler expectation: count_bytes_pending = 1
10:   4.        Reassembler action: insert "a" @ index 0
10:   5.        Writer expectation: bytes_pushed = 2
10:   6.        Reassembler expectation: count_bytes_pending = 0
10:   7.        Reader action: read "ab" and expect empty buffer after
10:   8.        Reader expectation: is_finished = false
10:   9.        Reassembler action: insert "bc" @ index 1 [last substring]
10:   10.       Writer expectation: bytes_pushed = 3
10:   11.       Reassembler expectation: count_bytes_pending = 0
10:   12.       Reader action: read "c" and expect empty buffer after
10:   ***** Unsuccessful Reader expectation: is_finished = true *****
10: 
10: Unmet Expectation: The Reader should have had is_finished = true, but instead it was false.
10: 
10: Exception: The test "insert last beyond capacity" failed because of an unmet expectation.
2/2 Test #10: reassembler_cap ..................***Failed    0.05 sec

The following tests passed:
        compile with bug-checkers

50% tests passed, 1 tests failed out of 2

Total Test time (real) =  14.36 sec

The following tests FAILED:
         10 - reassembler_cap (Failed)
Errors while running CTest
Output from these tests are in: /home/lily/cs144/minnow/build/Testing/Temporary/LastTest.log
Use "--rerun-failed --output-on-failure" to re-run the failed cases verbosely.

更新了_is_eof、_eof_index、_first_unassembled 就跑通了

#include "reassembler.hh"
#include "debug.hh"
#include <iostream>

using namespace std;

void Reassembler::insert(uint64_t first_index, string data, bool is_last_substring) {
  
  cerr<<"first index: "<<first_index<<"  string: "<<data<<endl;

  size_t data_size = data.size();
  size_t _first_unassembled = output_.writer().bytes_pushed();
  size_t _first_unaccept = _first_unassembled + _capacity;

  cerr<<"first_unaccembled : "<<_first_unassembled<<endl;

  //【这里注释掉也没问题】
  // // 只处理落在感兴趣区间内的数据
  // if (first_index >= _first_unaccept || first_index + data_size <= _first_unassembled) {
  //     if (is_last_substring) {
  //         _is_eof = true;
  //         _eof_index = first_index + data_size;
  //     }
  //     return;
  // }

  // 计算实际需要处理的数据区间
  size_t start = std::max(first_index, _first_unassembled);
  size_t end = std::min(first_index + data_size, _first_unaccept);

  cerr<<" "<<start<<" : "<<end<<endl;

  // 插入或更新数据
  //【值得注意的是,这里的插入方式是以单个字节来插入的!】
  for (size_t i = start; i < end; ++i) {
      size_t index = i - _first_unassembled;
      buffer[index] = data[i - first_index];
      cerr<<"buffered "<<buffer[index]<<endl;
  }

  // 检查并输出完整数据
  //【buffer.begin()->first == 0保证只取出紧挨着的字节】
  while (!buffer.empty() && buffer.begin()->first == 0) {
      std::string output;
      size_t start_index = 0;
      while (!buffer.empty() && buffer.begin()->first == start_index) {
          output += buffer.begin()->second;
          buffer.erase(buffer.begin());
          start_index++;
      }
      output_.writer().push(output);
      cerr<<"writed "<<output<<endl;
      _unassembled_bytes -= output.size();
      _first_unassembled += output.size();
  }

  // 更新未组装字节计数
  _unassembled_bytes += (end - start);

  if(is_last_substring){
    _is_eof = true;
    _eof_index = first_index + data_size;
  }
  // 检查EOF并关闭写入
  //【为啥结束条件是_first_unassembled == _eof_index?】
  if (_is_eof && _first_unassembled == _eof_index) {
      output_.writer().close();
  }

  cerr<<"eof_index: "<<_eof_index<<"  first_unassembled: "<<_first_unassembled<<endl;
}

// How many bytes are stored in the Reassembler itself?
// This function is for testing only; don't add extra state to support it.
uint64_t Reassembler::count_bytes_pending() const
{
  return _unassembled_bytes;
}
#pragma once

#include "byte_stream.hh"
#include <map>
#include <iostream>

using namespace std;

class Reassembler
{
public:
  // Construct Reassembler to write into given ByteStream.
  //调整构造函数,初始化一些成员变量
  //capacity:有什么根据吗
  explicit Reassembler( ByteStream&& output ) 
  : output_( std::move( output ) )
  , buffer() 
  , _is_eof( false )
  , _eof_index( 0 )
  , _capacity( output_.writer().available_capacity() )
  , _unassembled_bytes( 0 ) {
    cerr<<"##########################################3#########"<<endl;
  }


  /*
   * 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 );

  // How many bytes are stored in the Reassembler itself?
  // This function is for testing only; don't add extra state to support it.
  uint64_t count_bytes_pending() const;

  // Access output stream reader
  Reader& reader() { return output_.reader(); }
  const Reader& reader() const { return output_.reader(); }

  // Access output stream writer, but const-only (can't write from outside)
  const Writer& writer() const { return output_.writer(); }

private:
  ByteStream output_;
  size_t now_index = 0;
  std::map<uint64_t , std::string> buffer;
  bool _is_eof = false;
  size_t _eof_index = 0;
  size_t _capacity = 0;
  uint64_t _unassembled_bytes = 0;
};

相关文章:

  • 【Linux线程】——线程概念线程接口
  • 【Linux】Makeflie CMake快速上手指南
  • Conda常用命令汇总(持续更新中)
  • C语言【文件操作】详解中(会使用fgetc,fputc,fgets,fputs,fscanf,fprintf,fread,fwrite函数)
  • pfsense部署四(静态路由的配置)
  • 栈(Stack)和队列(queue)
  • 高斯数据库的空分区的查看和清理
  • 01 Overview
  • Mysql配套测试之查询篇
  • 网络不可达
  • [AI速读]CHISEL vs. SystemVerilog:用RISC-V核心对比两种硬件设计语言
  • milvus实战-基于Ollama+bge-large-zh搭建嵌入模型,fastAPI提供http服务将PDF文件写入milvus向量库
  • 算法·动态规划·入门
  • Parsing error: Unexpected token, expected “,“
  • 矩阵可相似对角化
  • 深入分析和讲解虚拟化技术原理
  • 洛谷 [语言月赛 202503] 题解(C++)
  • vlan路由间配置
  • 飞牛-NAS风扇速度设置
  • 1、双指针法
  • 黑龙江省政府副秘书长许振宇,拟任正厅级领导
  • 问责!美国海军对“杜鲁门”号航母一系列事故展开调查
  • 时隔3年俄乌直接谈判今日有望重启:谁参加,谈什么
  • 普京批准俄方与乌克兰谈判代表团人员名单
  • 七部门:进一步增强资本市场对于科技创新企业的支持力度
  • 陕西河南山西等地将现“干热风”灾害,小麦产区如何防范?