2025 cs144 Lab Checkpoint 2 小白超详细版
文章目录
- 1 环形索引的实现
- 1.1 wrap类
- wrap
- unwrap
- 2 实现tcp_receiver
- 2.1 tcp_receiver的功能
- 2.2 传输的报文格式
- TCPSenderMessage
- TCPReceiverMessage
- 2.3 如何实现函数
- receive()
- send()
1 环形索引的实现
范围是0~2^32-1
需要有SYN(逻辑开头)、FIN(逻辑结尾)
#pragma once#include <cstdint>/** The Wrap32 type represents a 32-bit unsigned integer that:* - starts at an arbitrary "zero point" (initial value), and* - wraps back to zero when it reaches 2^32 - 1.*/class Wrap32
{
public:explicit Wrap32( uint32_t raw_value ) : raw_value_( raw_value ) {}/* Construct a Wrap32 given an absolute sequence number n and the zero point. */static Wrap32 wrap( uint64_t n, Wrap32 zero_point );/** The unwrap method returns an absolute sequence number that wraps to this Wrap32, given the zero point* and a "checkpoint": another absolute sequence number near the desired answer.** There are many possible absolute sequence numbers that all wrap to the same Wrap32.* The unwrap method should return the one that is closest to the checkpoint.*/uint64_t unwrap( Wrap32 zero_point, uint64_t checkpoint ) const;Wrap32 operator+( uint32_t n ) const { return Wrap32 { raw_value_ + n }; }bool operator==( const Wrap32& other ) const { return raw_value_ == other.raw_value_; }protected:uint32_t raw_value_ {};
};
1.1 wrap类
表示一个32位无符号整数
需要实现wrap函数、unwrap函数
wrap
- 静态方法
- 将一个 64 位的绝对序列号 n 包装成一个 Wrap32 对象,以zero_point 作为起始点
static Wrap32 wrap( uint64_t n, Wrap32 zero_point );
unwrap
uint64_t unwrap( Wrap32 zero_point, uint64_t checkpoint ) const;
功能:该方法用于将当前的 Wrap32 对象解包成一个 64 位的绝对序列号。由于一个 Wrap32 对象可能对应多个 64 位的绝对序列号,因此需要一个 checkpoint 作为参考,返回最接近 checkpoint 的绝对序列号。
2 实现tcp_receiver
2.1 tcp_receiver的功能
receive方法用于接收消息,设置初始序列号并将数据推送至 Reassembler;
send方法用于生成并发送包含确认号和窗口大小的消息,整体预计约 15 行代码
2.2 传输的报文格式
TCPSenderMessage
struct TCPSenderMessage
{Wrap32 seqno { 0 };bool SYN {};std::string payload {};bool FIN {};bool RST {};// How many sequence numbers does this segment use?size_t sequence_length() const { return SYN + payload.size() + FIN; }
};
- seqno:序列的编号,就是索引的编号,
- SYN: 字节流的开始
- payload :传输的字符串
- FIN:字节流的结束
- RST: 是否reset
- sequence_length:总共有多少个seq
TCPReceiverMessage
struct TCPReceiverMessage
{std::optional<Wrap32> ackno {};uint16_t window_size {};bool RST {};
};
- ackno: 接收器需要的下一个seq编号
- window_size: 窗口大小,seq的大小限制?
- RST: reset
2.3 如何实现函数
照例,我们先来看头文件里函数的声明
void receive( TCPSenderMessage message );TCPReceiverMessage send() const;
receive()
照例,我们先来看头文件里函数的声明
void receive( TCPSenderMessage message );
receive是服务器端的接收,负责接收发送端传来的消息,再把消息传给重组器(insert)
以终为始,先看一下如果要往reassembler里插入消息的话需要哪些参数,就根据需求来从函数的参数里提取出来即可
void Reassembler::insert( uint64_t first_index, string data, bool is_last_substring )
data、is_last_substring可以直接提取,first_index是绝对索引,需要传来的Wrap格式的索引转换一下
因此就有了:
void TCPReceiver::receive( TCPSenderMessage message )
{//reset为啥要set_error?//const_cast: 去除对象的常量性,也就是说把const writer&转变为writer&if(message.RST == true){const_cast<Writer&>(reassembler_.writer()).set_error();}uint64_t stream_index;if(message.SYN){is_syn = true;_isn = message.seqno;stream_index = 0;}else{//checkpoint(参考点的设置):reassembler_.writer().bytes_pushed()表示当前写入输出流的字数// 新接收到的序列号通常会接近这个位置 stream_index = message.seqno.unwrap(message.seqno, reassembler_.writer().bytes_pushed());}reassembler_.insert(stream_index, message.payload, message.FIN);
}
send()
照例,我们先来看头文件里函数的声明
TCPReceiverMessage send() const;
主要任务就是返回一组【ackno(相对)、window_size、rst_flag】,重点就是如何计算相对索引(确认号)
确认号表示接收方 期望接收的下一个字节的 32 位相对序列号,计算步骤如下:
获取已接收并写入字节流的总字节数(bytes_pushed)。
处理 SYN 和 FIN 标志占用的序列号(各占一个字节)。
将绝对序列号转换为相对序列号(基于初始序列号 ISN)。
窗口大小表示 接收方当前还能接收的字节数,即 字节流缓冲区的剩余容量,在代码中,对应 ByteStream 的 available_capacity() 方法,此外,TCP 协议规定窗口大小字段为 16 位无符号整数(范围:0 ~ 65535),因此必须确保计算结果不超过 UINT16_MAX(65535),故
uint16_t window_size = static_cast<uint16_t>(min(reassembler_.writer().available_capacity(), static_cast<uint64_t>(UINT16_MAX))
);
因此,函数代码如下
TCPReceiverMessage TCPReceiver::send() const
{bool rst_flag = reassembler_.writer().has_error();//处理确认号(希望接受的字节的绝对序列号),流关闭的话需要加上FINuint64_t abs_ackno = reassembler_.writer().bytes_pushed() + 1;if(reassembler_.writer().is_closed()){abs_ackno += 1;}Wrap32 ackno = _isn + abs_ackno;// 计算窗口大小uint16_t window_size = static_cast<uint16_t>(min(reassembler_.writer().available_capacity(), static_cast<uint64_t>(UINT16_MAX)));if (!is_syn) {return {.ackno = std::optional<Wrap32>{},.window_size = window_size,.RST = rst_flag};}return {.ackno = ackno,.window_size = window_size,.RST = rst_flag};
}