CS144_2020_Fall_lab2(数据报接收器)

1 概述

在Lab 0中,你实现了流控制字节流(ByteStream)的抽象。在Lab 1中,你创建了一个StreamReassembler,它接受一系列子字符串,所有这些子字符串都来自同一个字节流,并将它们重新组装成原始流。这些模块将在你的TCP实现中证明其有用性,但它们中没有任何一个是特定于传输控制协议(TCP)的细节。这一点在Lab 2中发生了变化,你将实现TCPReceiver,这是TCP实现的一部分,用于处理传入的字节流。TCPReceiver在传入的TCP段(通过互联网传输的数据报的有效负载)和传入的字节流之间进行转换。

下面是上一次实验中的图表。TCPReceiver从互联网接收段(通过segment_received()方法),并将它们转化为对StreamReassembler的调用,最终写入传入的ByteStream。应用程序从这个ByteStream读取,就像你在Lab 0中通过从TCPSocket读取一样。

除了写入传入流之外,TCPReceiver还负责告诉发送方两件事:

1. 第一个未组装字节的索引,称为确认号(ackno)。这是接收方从发送方需要的第一个字节。
2. 第一个未组装索引和第一个不可接受索引之间的距离。这称为窗口大小。 ackno和窗口大小一起描述了接收方的窗口:TCP发送方被允许发送的索引范围。使用窗口,接收方可以控制传入数据的流动,使发送方限制其发送量,直到接收方准备好接收更多数据。我们有时将ackno称为窗口的左边缘(TCPReceiver感兴趣的最小索引),ackno +窗口大小称为右边缘(TCPReceiver感兴趣的最大索引之外的位置)。

在编写StreamReassembler和ByteStream时,你已经完成了实现TCPReceiver所涉及的大部分算法工作;这个实验是将这些通用类连接到TCP的细节。最困难的部分涉及思考TCP将如何表示流中每个字节的位置,即序列号。

前面我们已经完成了基础部分的实现,从lab2开始,我们就要开始实现与传输有关的角色了。

首先让我们了解几个关键词
ackno:确认号,32位,用于告知发送方第一个未重组字符的位置,以便发送方更新他的重传缓冲区,如果实在不理解,可以先不考虑发送方,后续都会做到。
win:窗口大小,16位,流量控制是老生常谈的问题,而win就是流量控制的核心,接收方需要向发送方报告自己还能够接收多少比特。


3 The TCP Receiver


TCP是一种在不可靠数据报上可靠传递一对受流控制的字节流(每个方向一个)的协议。TCP连接涉及两方,每一方同时充当发送方(发送自己的传出字节流)和接收方(接收传入字节流)。这两方被称为连接的端点,或对等方。

在本周,你将实现TCP的接收方部分,负责接收TCP段(实际的数据报有效载荷),重组字节流(包括其结束时),并确定需要发送回发送方以进行确认和流量控制的信号。

为什么要做这个?这些信号对于TCP提供在不可靠数据报网络上受流控制的可靠字节流的服务至关重要。在TCP中,确认意味着:“接收方需要下一个字节的索引,以便它可以重新组装更多的ByteStream。”这告诉发送方需要发送或重新发送哪些字节。流量控制意味着:“接收方对哪个索引范围感兴趣并愿意接收?”(通常是其剩余容量的函数)。这告诉发送方允许发送多少。

记住最上面的那段换,TCP连接涉及两方,每一方同时充当发送方(发送自己的传出字节流)和接收方(接收传入字节流)。这两方被称为连接的端点,或对等方,第一次做这个实验到lab4我都不是很理解,就因为这句话没有看到,我一直以为的是客户端是receiver,发送端是sender,但是仔细一想连接的双方不都发送接收数据吗,于是才找到了这句话来论证自己的观点。

3.1 64位索引和32位索引

这一大段非常长,我就不给大家翻译了,大家自己看一遍之后再来往下看。

我们知道,在上一个lab中的push_substring的参数中,有一个index的参数,用来传入该报文段在正确顺序中的位置信息,但是你们是否注意过该index的类型是64位的,但是在实际传输过程中,由于此类信息都记录于数据报头部,而头部的空间有限,我们无法存下那么大规模的数字,所以数据报头中的索引值仅仅是32位的,这里就涉及到了转化的问题。先不考虑实现,我们看一下数据报头部结构,理解几个数据报头重的新名词

我们可以在header文件中看到这样一大段

来封装数据报头的函数,可以看到诸多头部信息。
今天我们要用的有几个

1.SYN:标志着传输开始标志
2.FIN:标志着传输已经结束的标志
3.Absolute Sequence Numbers:为正常字符串下标,即从 00 开始依次加一。(SYN 和 FIN 都要占据一个位置,SYN 在 00,FIN 在最后一个字符后面一个)
4.Sequence Numbers:用 ISN 表示 SYN 的位置(在实际应用中 ISN 为一个32位的随机正整数),而对于某个字符串的字符,如果它的 Absolute Sequence Number 为x,那么它的 Sequence Number 为 (ISN+x)mod(2^32)
5.Stream Indices:则是 Absolute Sequence Numbers 去掉 SYN 和 FIN
6.ISN:是TCP连接中的一个随机32位数字,用于提高安全性并防止混淆先前连接之间的旧段。TCP通过确保序列号不能被猜测并且不太可能重复来实现这一点。

这几个,就对应了他给出的这个图

教我们来辨析这几个名词。

这几个名词在特点的地方有特定的用处,而他们的互相转换就显得尤为重要

为了使绝对序列号和流索引之间的转换变得简单,只需加减一即可。然而,将序列号和绝对序列号之间进行转换则有些困难,混淆两者可能导致棘手的错误。为了系统地防止这些错误,我们将使用一个自定义类型来表示序列号:WrappingInt32,并编写与其与绝对序列号(用uint64_t表示)之间的转换函数。

WrappingInt32是一个包装类型的示例:它包含一个内部类型(在本例中是uint32_t),但提供了一组不同的函数/运算符。

我们已经为你定义了这个类型并提供了一些辅助函数(请参阅`wrapping_integers.hh`),但你将在`wrapping_integers.cc`中实现转换操作。

1. `WrappingInt32 wrap(uint64_t n, WrappingInt32 isn)`:给定绝对序列号(n)和初始序列号(isn),产生相对于n的序列号。
    
2. `uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint)`:给定一个序列号(n)、初始序列号(isn)和一个绝对检查点序列号,计算与n最接近的绝对序列号。注意:由于任何给定的序列号都对应于许多绝对序列号。例如,对于ISN为零,序列号17对应于绝对序列号17,但也对应于232 + 17、233 + 17、234 + 17等等。检查点有助于解决这种歧义:它是该类的用户知道在正确答案附近的绝对序列号。在这里,“在正确答案附近”可以意味着与正确答案相差不超过231的任何64位数字。在你的TCP实现中,你将使用最后重组的字节的索引作为检查点。
    

提示:最干净/最简单的实现将使用`wrapping_integers.hh`中提供的辅助函数。wrap/unwrap操作应保留偏移量,即两个序列号之间相差17的情况应该对应于两个绝对序列号之间也相差17的情况。

你可以通过运行WrappingInt32测试来测试你的实现。从构建目录中运行`ctest -R wrap`。

我们可以查看lab2分支中的wrapping_intergers.hhwrapping_intergers.cc文件,来看我们需要做的事情,我们需要实现32位和64位地址的互相转换,64转32很简单,加ISN取模就可以了,而32转64就会出现很多问题,因为会出现同余的问题,他给了一个checkpoint参数就解决了这个问题,我们只需要找个最接近这个参数的那个数就可以了,这个问题就是一个简单的数学问题了。

我们可以那就看checkpoint%mod和(n-b)%mod这两个的差和其中小的加一个mod的差取小的
就是我们需要的答案。

这里挂上代码实现。
wrapping_integers.hh
wrapping_integers.cc
然后我们make && ctest -R wrap测试就可以了

3.2 接收函数实现

接下来我们就可以继续接收方API的实现了。

祝贺您成功实现了包装和解包装逻辑!在本实验的下一步中,您将处理TCPReceiver,其职责是(1)从对等方接收段,(2)使用StreamReassembler重新组装ByteStream,并(3)计算确认号(ackno)和窗口大小。这些值最终将在传出段中发送回对等方。

首先,请花点时间查看TCP段的格式。这是两个端点之间交换的消息,构成更低级数据报的有效载荷。对于本实验,相关字段包括序列号、有效负载以及SYN和FIN标志。这些字段由发送方编写,由接收方读取/执行。C++中的TCPSegment类表示此消息,您可以参考TCPSegment和TCPHeader的文档以获取更多详细信息。

TCPReceiver类将提供以下接口:
// 构造一个最多可存储capacity字节的TCPReceiver
TCPReceiver(const size_t capacity); // 在.hh文件中已为您实现

// 处理传入的TCP段
void segment_received(const TCPSegment &seg);

// 应发送到对等方的确认号
// 如果没有接收到SYN,则返回空
// 这是接收方窗口的开始,即接收方尚未接收的流中的第一个字节的序列号。
std::optional<WrappingInt32> ackno() const;

// 应发送到对等方的窗口大小
// 形式上:这是接收方愿意接受的索引窗口的大小。这是第一个未组装和第一个不可接受的索引之间的距离。
// 换句话说:它是容量减去TCPReceiver在字节流中持有的字节数。
size_t window_size() const;

// 存储但尚未重新组装的字节数
size_t unassembled_bytes() const; // 在.hh文件中已为您实现

// 访问重新组装的字节流
ByteStream &stream_out(); // 在.hh文件中已为您实现

TCPReceiver旨在与您的StreamReassembler配合使用。构造函数和unassembled_bytes()方法已经为您实现,您需要完成其他方法的实现。祝您好运!

又到了喜闻乐见的读代码环节了,他让我们来了解一下TCP头部有什么东西,这里我前面就介绍过了,虽然不全面,但是是lab2的重点,后续我们会接触更多头部中的标志。对于接收方的逻辑,其实比较简单,整个代码量也不是很大,先看segment_received函数,当接收到数据报碎片后,如果是发送方的syn数据报,那么数据接收方将会更新isn,以便后续ackno的计算,当然,我们还需要将接收到的数据报塞到我们的入向字节流中,所以上一个lab中实现的内容就派上了用场。后面就没什么特别注意的了,无非是数值转换不啦不啦的。

最后写完了就直接make check_lab2就可以了

附上代码
tcp_receiver.hh
tcp_receiver.cc

至此,lab2就结束了。

posted @ 2024-03-07 14:10  AtongM  阅读(89)  评论(0编辑  收藏  举报