CS 144 Lab Two -- TCPReceiver

news2024/12/27 17:13:02

CS 144 Lab Two -- TCPReceiver

  • TCPReceiver 简述
    • 索引转换
    • TCPReceiver 实现
  • 测试


对应课程视频: 【计算机网络】 斯坦福大学CS144课程

Lab Two 对应的PDF: Lab Checkpoint 2: the TCP receiver


TCPReceiver 简述

在 Lab2,我们将实现一个 TCPReceiver,用以接收传入的 TCP segment 并将其转换成用户可读的数据流。
在这里插入图片描述

TCPReceiver 除了将读入的数据写入至 ByteStream 中以外,它还需要告诉发送者两个属性

  • 第一个未组装的字节索引,称为确认号ackno,它是接收者需要的第一个字节的索引。
  • 第一个未组装的字节索引第一个不可接受的字节索引之间的距离,称为 窗口长度window size

ackno 和 window size 共同描述了接收者当前的接收窗口。接收窗口是 发送者允许发送数据的一个范围,通常 TCP 接收方使用接收窗口来进行流量控制,限制发送方发送数据。

总的来说,我们将要实现的 TCPReceiver 需要做以下几件事情:

  • 接收TCP segment
  • 重新组装字节流(包括EOF)
  • 确定应该发回给发送者的信号,以进行数据确认和流量控制

索引转换

TCP 报文中用来描述当前数据首字节的索引(序列号 seqno)是32位类型的,这意味着在处理上增加了一些需要考虑的东西:

  • 由于 32位类型最大能表达的值是 4GB,存在上溢的可能。因此当 32位的 seqno 上溢后,下一个字节的 seqno 就重新从 0 开始。

  • 处于安全性考虑,以及避免与之前的 TCP 报文混淆,TCP 需要让每个 seqno 都不可被猜测到,并且降低重复的可能性。因此 TCP seqno 不会从 0 开始,而是从一个 32 位随机数起步(称为初始序列号 ISN)。

    • 而 ISN 是表示 SYN 包(用以表示TCP 流的开始)的序列号。
  • TCP 流的逻辑开始数据包逻辑结束数据包各占用一个 seqno。除了确保接收到所有字节的数据以外,TCP 还需要确保接收到流的开头和结尾。 因此,在 TCP 中,SYN(流开始)和 FIN(流结束)控制标志将会被分别分配一个序列号(SYN标志占用的序列号就是ISN)。

    • 流中的每个数据字节也占用一个序列号。
    • 但需要注意的是,SYN 和 FIN 不是流本身的一部分,也不是传输的字节数据。它们只是代表字节流本身的开始和结束。

字节索引类型一多就容易乱。当前总共有三种索引:

  • 序列号 seqno。从 ISN 起步,包含 SYN 和 FIN,32 位循环计数
  • 绝对序列号 absolute seqno。从 0 起步,包含 SYN 和 FIN,64 位非循环计数
  • 流索引 stream index。从 0 起步排除 SYN 和 FIN64 位非循环计数。

这是一个简单浅显的例子,用于区分开三种索引的区别:

在这里插入图片描述
序列号和绝对序列号之间相互转换稍微有点麻烦,因为序列号是循环计数的。在该实验中,CS144 使用自定义类型 WrappingInt32 表示序列号,并编写了它与绝对序列号之间的转换。
在这里插入图片描述

  • wrapping_integers.cc
//! Transform an "absolute" 64-bit sequence number (zero-indexed) into a
//! WrappingInt32 \param n The input absolute 64-bit sequence number \param isn
//! The initial sequence number
// 将64位绝对序列号转换为32位序列号   
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) { return WrappingInt32{isn + static_cast<uint32_t>(n)}; }

//! Transform a WrappingInt32 into an "absolute" 64-bit sequence number
//! (zero-indexed) \param n The relative sequence number \param isn The initial
//! sequence number \param checkpoint A recent absolute 64-bit sequence number
//! \returns the 64-bit sequence number that wraps to `n` and is closest to
//! `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One
//! stream runs from the local TCPSender to the remote TCPReceiver and has one
//! ISN, and the other stream runs from the remote TCPSender to the local
//! TCPReceiver and has a different ISN.
// 将TCP协议头中携带的32位序列号转换为64位绝对序列号
// 参数: 要转换的32位序列号,本次TCP连接的ISN(初始序列号),检查点(一个32位序列号对应多个64位序列号,因此这里选择靠近ISN的值)
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
    uint64_t offset = uint32_t(n - isn);
    if (checkpoint < offset)
        return offset;
    offset += ((checkpoint - offset) >> 32) * (1lu << 32);
    return checkpoint - offset <= (1lu << 31) ? offset : offset + (1lu << 32);
}

TCPReceiver 实现

需要实现一些类成员函数

  • segment_received(): 该函数将会在每次获取到 TCP 报文时被调用。该函数需要完成:

    • 如果接收到了 SYN 包,则设置 ISN 编号。
      • 注意:SYN 和 FIN 包仍然可以携带用户数据并一同传输。同时,同一个数据包下既可以设置 SYN 标志也可以设置 FIN 标志
      • 将获取到的数据传入流重组器,并在接收到 FIN 包时终止数据传输。
  • ackno():返回接收方尚未获取到的第一个字节的字节索引。如果 ISN 暂未被设置,则返回空。

  • window_size():返回接收窗口的大小,即第一个未组装的字节索引第一个不可接受的字节索引之间的长度。

这是 CS144 对 TCP receiver 的期望执行流程:
在这里插入图片描述
三次握手:

在这里插入图片描述
实现思路:

对于 TCPReceiver 来说,除了错误状态以外,它一共有3种状态,分别是:

  • LISTEN:等待 SYN 包的到来。若在 SYN 包到来前就有其他数据到来,则必须丢弃
  • SYN_RECV:获取到了 SYN 包,此时可以正常的接收数据包
  • FIN_RECV:获取到了 FIN 包,此时务必终止 ByteStream 数据流的输入。

在每次 TCPReceiver 接收到数据包时,我们该如何知道当前接收者处于什么状态呢?可以通过以下方式快速判断:

  • 当 isn 还没设置时,肯定是 LISTEN 状态
  • 当 ByteStream.input_ended(),则肯定是 FIN_RECV 状态
  • 其他情况下,是 SYN_RECV 状态

Window Size 是当前的 capacity 减去 ByteStream 中尚未被读取的数据大小,即 reassembler 可以存储的尚未装配的子串索引范围。

ackno 的计算必须考虑到 SYN 和 FIN 标志,因为这两个标志各占一个 seqno。故在返回 ackno 时,务必判断当前 接收者处于什么状态,然后依据当前状态来判断是否需要对当前的计算结果加1或加2。而这条准则对 push_substring 时同样适用。


源码:

  • tcp_receiver.hh
//! Receives and reassembles segments into a ByteStream, and computes
//! the acknowledgment number and window size to advertise back to the
//! remote TCPSender.
class TCPReceiver {
  //! Our data structure for re-assembling bytes.
  StreamReassembler reassembler_;
  //! The maximum number of bytes we'll store.
  size_t capacity_;
  // The absolute seqno
  uint64_t seqno_;
  // The initial sequence nummber
  std::optional<WrappingInt32> isn_;
  // The Fin seqno
  std::optional<uint64_t> fin_seq_;

 public:
  TCPReceiver(const size_t capacity)
      : reassembler_(capacity),
        capacity_(capacity),
        seqno_(0),
        isn_(),
        fin_seq_() {}

  //! The ackno that should be sent to the peer
  //! \returns empty if no SYN has been received
  //!
  //! This is the beginning of the receiver's window, or in other words, the
  //! sequence number of the first byte in the stream that the receiver hasn't
  //! received.
  std::optional<WrappingInt32> ackno() const;
  //! \brief The window size that should be sent to the peer
  //!
  //! Operationally: the capacity minus the number of bytes that the
  //! TCPReceiver is holding in its byte stream (those that have been
  //! reassembled, but not consumed).
  //!
  //! Formally: the difference between (a) the sequence number of
  //! the first byte that falls after the window (and will not be
  //! accepted by the receiver) and (b) the sequence number of the
  //! beginning of the window (the ackno).
  size_t window_size() const;
  // number of bytes stored but not yet reassembled
  size_t unassembled_bytes() const { return reassembler_.unassembled_bytes(); }
  // handle an inbound segment
  void segment_received(const TCPSegment &seg);
  ByteStream &stream_out() { return reassembler_.stream_out(); }
  const ByteStream &stream_out() const { return reassembler_.stream_out(); }
};
  • tcp_receiver.cc
void TCPReceiver::segment_received(const TCPSegment &seg) {
  // check syn
  // tcp头中syn标志被设置了---记录初始序列号
  if (seg.header().syn) {
    isn_ = seg.header().seqno;
  }
  // 如果初始化序列号还没有设置,说明TCP连接还没有建立,忽略当前传入的数据包
  if (!isn_.has_value()) return;

  // check fin
  // tcp头中fin标志被设置了 -- 记录结束序列号
  if (seg.header().fin)
    fin_seq_ = unwrap(seg.header().seqno, isn_.value(), seqno_) +
               seg.length_in_sequence_space();

  // compute index(absolute seqno)
  // 将当前TCP报文的32位序列号转换为绝对序列号
  uint64_t index = unwrap(seg.header().seqno, isn_.value(), seqno_);
  // 如果syn标志设置了,那么减去SYN占用的序列号 -- SYN包也可以携带用户数据
  if (!seg.header().syn) index--;
  // 将TCP载荷数据推入流重组器中: 字节流,该批字节流起始的序列号,当前字节流是否是最后一批数据取决于当前TCP报文的fin标志是否设置了
  reassembler_.push_substring(seg.payload().copy(), index, seg.header().fin);
  // update the seqno
  // 更新下一个期望接收到的字节起始序列号 --> 也就是ack给发送者的seqno
  seqno_ = reassembler_.stream_out().bytes_written() + 1;
  // 如果fin标志被设置了,那么检查点序列号还需要+1 --> fin_seq也占据一个seqno
  if (fin_seq_.has_value() && fin_seq_.value() == seqno_ + 1) seqno_++;
}

// 如果连接已经建立,那么返回的ackno值就是seqno_
optional<WrappingInt32> TCPReceiver::ackno() const {
  return isn_.has_value() ? wrap(seqno_, isn_.value()) : isn_;
}

// 当前滑动窗口大小
size_t TCPReceiver::window_size() const {
  return capacity_ - (reassembler_.stream_out().bytes_written() -
                      reassembler_.stream_out().bytes_read());
}

需要注意的是 TCPReceiver 接收到的是 TCP 报文段 TCPSegment, 其中报文首部记录的均为序列号(seqno), 而 TCPReceiver 内部使用的 StreamReassembler 实际上使用的是流索引(stream index), 过程中需要借助 unwrap() 及 wrap() 函数进行转换. 而这其中就需要使用 ISN 进行转换, 因此需要添加一个 _isn 的私有成员记录该 TCP 连接的 ISN. 值得一提的是, 在未收到 SYN 标志位时, 没有 ISN, 因此最终使用 std::optional< WrappingInt32 > 作为 _isn 的类型.

对于 segment_received() 函数, 需要注意的有: 在接收到 SYN 报文段之前的报文都是无效报文, 需要丢弃不做处理. 在转换序列号到流索引时, 需要一个检查点(checkpoint), 根据指导书前文, 检查点是最后一个重组字节的相对序列号, 而 stream_out().bytes_written() 表示已经写入 ByteStream 字节流的字节数, 其值与最后一个重组的字节的相对序列号一致. 同时在使用 unwrap() 时需要注意 ISN 同样占一个序列号, 因此对于其负载的数据的序列号需要额外加 1.

对于 ackno() 函数, 在 ISN 未设置前需要返回空, 即 std::nullopt, 反之返回下一个字节的序列号. stream_out().bytes_written() 表示的为最后重组字节的相对序列号, 加 1 即第一个未重组字节的相对序列号, 再通过 wrap() 即可转换为序列号. 同样需要注意, FIN 标志位也占用一个序列号, 因此在收到 FIN 之后, 序列号还要再加 1.

对于 window_size(), 即第一个未重组字节和第一个不接受字节的间距, 也就是除去已重组的字节的空间大小. 由于 ByteStream 和 StreamRessembler 总容量为一致, 因此可以用 stream_out().remaining_capacity() 表示.

在这里插入图片描述


测试

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/771070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI读心术:情感分析和数据标注的奥秘

情感分析也被称为情感分类&#xff0c;意图挖掘&#xff1b;是让机器去辨别和理解人类的情感语言文本的技术。互联网技术发展至今&#xff0c;人人都和手机形影不离&#xff0c;任何消费行为、生活休闲、美食评论、旅行决策都可以通过网络的连接让信息共享和公开。商家也利用这…

C++第四讲

思维导图 仿照string类&#xff0c;实现myString类 /* ---------------------------------author&#xff1a;YoungZorncreated on 2023/7/19 19:20.--------------------------------- */ #include<iostream> #include<cstring>using namespace std;class myStri…

每天一点Python——day58

#第五十八天 集合间的关系&#xff1a; 类似于数学中学到的集合一样&#xff0c;关系差不多&#xff0c;譬如相等&#xff0c;子集&#xff0c;交集 如图所示&#xff1a;#①两个集合是否相等&#xff1a;运用运算符【等号】或者运算符&#xff01;【不等号】进行判断 #例&…

如何生成一个漂亮的allure测试报告

前言 今天给大伙展示一下如何生成一个漂亮的allure测试报告&#xff0c;同时呢希望能帮助到大家。 定制化后的allure测试报告效果展示 如何定制化输出锦上添花的allure测试报告 使用前&#xff0c;先导入allure模块。 import allure 使用前&#xff0c;先熟悉运行测试用例…

【C++】STL---vector基本用法介绍

个人主页&#xff1a;平行线也会相交&#x1f4aa; 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】&#x1f48c; 本专栏旨在记录C的学习路线&#xff0c;望对大家有所帮助&#x1f647;‍ 希望我们一起努力、成长&…

异步任务——CompletabelFuture

本专栏学习内容又是来自尚硅谷周阳老师的视频 有兴趣的小伙伴可以点击视频地址观看 在学习CompletableFuture之前&#xff0c;必须要先了解一下Future Future 概念 Future接口&#xff08;FutureTask实现类&#xff09;定义了操作异步任务执行的一些方法&#xff0c;如获取异…

编写测试用例的方法,这个是真的很好用

大家测试过程中经常用的等价类划分、边界值分析、场景法等&#xff0c;并不能覆盖所有的需求&#xff0c;我们之前讲过很少用到的因果图法&#xff0c;下面就来讲另一种不经常用到但又非常重要的测试用例编写方法——测试大纲法。 测试大纲法适用于有多个窗口&#xff0c;每个…

Mysql下载详细步骤

一、下载mysql 打开地址&#xff1a;MySQL :: Download MySQL Community Server 这里我下载的是红框标注的。 直接点击No thanks,just start my download.解压后文件看自己需求放置。 红框圈住的文件都是后面自己添加的。 my文件中需要添加的文本内容。 [mysqld] #设置3306端口…

【conan】本地编译三方库,上传conan服务器

1.6 conan 远程已经编译好的库 conan中文博客&#xff1a; 三方库资源&#xff1a; github conan-io 本地查询 conan search Existing package recipes:b2/4.9.6 boost/1.71.0nolovr/stable bzip2/1.0.8 ceres-solver/2.0.0nolovr/stable eigen/3.3.7nolovr/stable eigen_c…

pdf转换成word怎么转换?简单快捷方法分享

pdf转换成word怎么转换&#xff1f;需要将PDF文档转换为Word文档&#xff0c;以便更好地编辑和修改文本内容。比如&#xff0c;当你需要对一份PDF文档中的内容进行修改、编辑或者格式化时&#xff0c;你可以先将其转换为Word文档&#xff0c;再进行修改。这样可以更快速、更便捷…

【数据结构与算法】哈夫曼编码(最优二叉树实现

哈夫曼编码 等长编码&#xff1a;占的位置一样 变长编码&#xff08;不等长编码&#xff09;&#xff1a;经常使用的编码比较短&#xff0c;不常用的比较短 最优&#xff1a;总长度最短 最优的要求&#xff1a;占用空间尽可能短&#xff0c;不占用多余空间&#xff0c;且不…

4.BIO多线程即时通信

highlight: arduino-light 基于BIO模式下的即时通信&#xff0c;我们需要解决客户端到客户端的通信&#xff0c;也就是需要实现客户端与客户端的端口消息转发逻辑。 功能清单 1.客户端登陆功能 可以启动客户端进行登录&#xff0c;客户端登陆只需要输入用户名和服务端ip地址即可…

JVM理论(五)执行引擎--解释器/JIT编译器

概述 首先执行引擎是java虚拟机核心的组成部分之一;而JVM的主要任务是装载字节码到内存,但不能够直接运行在操作系统之上.因为字节码指令并非等价于本地机器指令,它仅仅包含能够被JVM所识别的指令、符号表、以及其他信息;而此时执行引擎就华丽登场,它的任务就是将字节码指令解…

欧姆龙PLC联网

一、设备信息确认 左上角的为PLC型号,如图该PLC型号为CP1H,不同型号的欧姆龙PLC通讯方面有什么差别呢? 通讯能力和方式不同: 有些型号PLC自带网口,有些则需要扩展(上图中右侧的两个红框内为后扩展的通讯口,扩展模块可以随意组合双网口,双232串口,双485串口都可以)…

D354周赛复盘:特殊元素平方和+数组最大美丽值(滑动窗口)+合法分割最小下标

文章目录 6889.特殊元素平方和思路完整版取模注意&#xff1a;不能对0取余/取模解答错误&#xff1a;本题的数组最后一个下标是nums[nums.size()] 6929.数组的最大美丽值&#xff08;排序滑动窗口&#xff09;思路1&#xff1a;排序滑动窗口注意点 6927. 合法分割的最小下标&am…

My_window类(带有next和quit按钮)

运行代码&#xff1a; //My_window类&#xff08;带有next和quit按钮&#xff09; #include"std_lib_facilities.h" #include"GUI/Simple_window.h" #include"GUI/GUI.h" #include"GUI/Graph.h" #include"GUI/Point.h"//--…

为什么项目可见性难以实现?该如何提高?

在项目和专业服务管理中&#xff0c;失败有时难以避免。沟通不足和需求定义不明确被认为是造成失败的最大原因&#xff0c;这意味着项目可见性和信息流动至关重要。 什么是项目可见性&#xff1f; 项目可见性是组织项目相关信息的方式&#xff0c;以便所有团队成员、项目经理…

火狐安卓版支持油猴了!后面将支持更多扩展插件

日前火狐浏览器每夜构建版的安卓版已经带来了更多扩展程序支持&#xff0c;这其中就包括大名鼎鼎的油猴扩展程序。本次火狐浏览器每夜构建版更新新增五款扩展程序支持&#xff0c;并且按照谋智基金会说法还会支持更多的扩展程序。 下载地址&#xff1a;https://ftp.mozilla.org…

力扣 406. 根据身高重建队列

题目来源&#xff1a;https://leetcode.cn/problems/queue-reconstruction-by-height/description/ C题解1&#xff1a;分别对h和k两个维度进行考虑&#xff0c;我这里是优先考虑k值&#xff0c;k值相同的时候h小的排前面。然后再一一遍历&#xff0c;对于people[i]&#xff0c…

曲师大2023大一新生排位赛-D.Factor题解

D.Factor 题目描述 你有一个集合 &#xff0c;和具有 个正整数的数组 . 最初&#xff0c;集合 为空&#xff08;不包含任一元素&#xff09;。你将按照以下方式填充集合 : 以此枚举数组 a 中的每个元素。对于数组中的第 i 个元素 &#xff0c;生成 ​ 的因子集合 ​。如果…