一、概述
实验作业将要求我们以模块化的方式构建TCP实现:
1.在实验1中,我们将实现一个流重组器,该模块将字节流的小段(称为子字符串或段)重组回正确序列的连续字节流。
2.在实验2中,我们将实现处理入站字节流的TCP部分: TCPReceiver。这涉及到TCP将如何表示每个字节在流中被称为\序列号的|中的位置。”tcprecreceiver负责告诉发送方(a)它已经成功组装了多少入站字节流(这叫做\确认”)和(b)发送方现在还允许发送多少字节(\流控制”)
3.在实验3中,我们将实现处理输出字节流的TCP部分:
TCPSender。当发送方怀疑它传输的一个片段在途中丢失了,没有到达接收方时,它应该如何反应?它应该在什么时候再次尝试并重新发送丢失的片段?
4.在实验4中,我们将结合以前的工作和实验的工作来创建一个工作
TCP实现:一个包含TCPSender和TCPReceiver的TCPConnection。
我们将使用它与世界各地的真实服务器进行通信。
二、开始
在sponge目录下运行
git fetch
git merge origin/lab1-startercode
得到lab1的源码
在build目录下make
三、按顺序放置子字符串
TCP发送方将其字节流分成较短的段(每个子串不超过1460个字节),以便每个子串都能装入一个数据报中。但是网络可能会对这些数据报进行重新排序,或者丢弃它们,或者多次发送它们。接收端必须将这些段重新组装成它们开始时的连续字节流。
在本实验中,我们将实现一个流重组器(stream reassembler),可以将带索引的字节流碎片重组成有序的字节流。重组完的字节流应当被送入指定的字节流(byte stream)对象_output中。
分析:
//! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes.
//! \note This capacity limits both the bytes that have been reassembled,
//! and those that have not yet been reassembled.
StreamReassembler(const size_t capacity);
重组器的构造方法。重组器有容量控制,其内的字节流(已重组好的和未重组好的)不能超过容量,超出部分需要丢弃。
//! \brief Receive a substring and write any newly contiguous bytes into the stream.
//!
//! The StreamReassembler will stay within the memory limits of the `capacity`.
//! Bytes that would exceed the capacity are silently discarded.
//!
//! \param data the substring
//! \param index indicates the index (place in sequence) of the first byte in `data`
//! \param eof the last byte of `data` will be the last byte in the entire stream
void push_substring(const std::string &data, const uint64_t index, const bool eof);
接收子串,并将新的连续的字节写入流中。只写入新的,如果有重合部分则丢弃重合部分。这里也要注意容量,超出容量部分要丢弃。
data:子串内容
index:’ data '中第一个字节的索引(按顺序排列)
eof:data的最后一个字节是否为整个流的最后一个字节
//! \name Access the reassembled byte stream
//!@{
const ByteStream &stream_out() const {
return _output; }
ByteStream &stream_out() {
return _output; }
//!@}
访问重组好的字节流
//! The number of bytes in the substrings stored but not yet reassembled
//!
//! \note If the byte at a particular index has been pushed more than once, it
//! should only be counted once for the purpose of this function.
size_t unassembled_bytes() const;
已存储但未被重组的字节流数
//! \brief Is the internal state empty (other than the output stream)?
//! \returns `true` if no substrings are waiting to be assembled
bool empty() const;
重组器是否为空
3.1 FAQs
1.整个流中第一个字节的索引是什么?零。
2.我的实现应该有多高效?我们不打算指定一个特定的概念的效率,但请不要把这作为一个挑战来构建一个空间或时间低效的数据结构,这个数据结构将是您的TCP实现的基础。
3.应该如何处理不一致的子字符串?你可以假设它们不存在。也就是说,您可以假设存在一个惟一的底层字节流,并且所有的子字符串都是它的(准确的)片段
4.我可以用什么?您可以使用标准库中任何您认为有用的部分。特别是,我们希望您至少使用一种数据结构。
5.什么时候应该将字节写入流?越快越好。一个字节不应该出现在流中的唯一情况是,在它之前有一个字节还没有被推入。
6.子重叠吗?是的。
7.我是否需要向StreamReassembler添加私有成员?是的。由于段可能以任何顺序到达,你的数据结构将不得不记住“子字符串,直到它们准备放入流“,也就是说,直到它们之前的所有索引都被填满。
以下参考segmentfault
注意点:
- 报文段包含索引、长度、内容,lab1 的索引从 0 开始增长,不会溢出绕回。
- 任何报文段,包括新收到的和暂存的,只要可以,就应该立刻送入 ByteStream(可能需要手动重组和去重叠)。
- 容量的限制如下图所示。
思路:
流重组器首先要选择一个数据结构,要储存:data、首索引、长度,使用结构体
接收子串:超过容量的部分丢弃,剩下的丢给重叠处理
重叠处理:前面部分重叠、后面部分重叠、两者都有、两者都无,合并两个子串
已存储但未被重组的字节流数:需要三个变量存储:最早未读、最早未被重组、最早未被接收。后两个坐标之间的子串长度相加
重组器是否为空:变量_is_end存储字节流是否到达结尾,如果字节流到达结尾并且重组器为空,则返回是
根据FAQs5,还需要一个函数,将重组好的片段送入ByteStream
代码:
.hh
class StreamReassembler {
private:
// Your code here -- add private members as necessary.
ByteStream _output; //!< The reassembled in-order byte stream. ByteStream类,实现名为_output
size_t _capacity; //!< The maximum number of bytes
size_t _first_unread = 0;//最早未读的下标
size_t _first_unassembled = 0;//最早未重组的下标
size_t _first_unacceptable;//最早未被接收的下标
bool _eof = false;//字符流是否全部被重组完(到达结尾)
struct seg {
//定义seg结构:首字节的index;子串长度;子串内容;重载运算符<
size_t index;
size_t length;
std::string data;
bool operator<(const seg t) const {
return index < t.index; }
};
std::set<seg> _stored_segs = {
};//实现seg结构:已被存储的子串
void _add_new_seg(seg &new_seg, const bool eof);//添加新段
void _handle_overlap(seg &new_seg);//处理重叠部分
void _stitch_output();//将重组好的所有段送入ByteStream
void _stitch_one_seg(const seg &new_seg);//将重组好的某一段送入ByteStream中
void _merge_seg(seg &new_seg