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)中,不同的协议类型被分配了不同的数字代码,这些代码用于标识数据包使用的协议类型。
- 协议号 5:这通常指的是Internet控制消息协议(ICMP)。ICMP用于在IP主机、路由器之间传递控制消息。它被用于诊断网络问题,例如,当你尝试访问一个不可达的主机时,可能会收到一个ICMP消息(例如,ping请求和响应)。
- 协议号 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;
};