CS 144 Lab Two -- TCPReceiver

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 seqnouint64_t seqno_;// The initial sequence nummberstd::optional<WrappingInt32> isn_;// The Fin seqnostd::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 reassembledsize_t unassembled_bytes() const { return reassembler_.unassembled_bytes(); }// handle an inbound segmentvoid 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给发送者的seqnoseqno_ = reassembler_.stream_out().bytes_written() + 1;// 如果fin标志被设置了,那么检查点序列号还需要+1 --> fin_seq也占据一个seqnoif (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.mzph.cn/news/3882.shtml

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

相关文章

【深度学习笔记】训练 / 验证 / 测试集

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面 虽然说IE6除了部分要求苛刻的需求以外已经被可以不考虑了&#xff0c;但是WIN7自带的浏览器IE8还是需要支持的。 本文这个方法主要的优点&#xff0c;个人觉得就是准备少&#xff0c;不需要上网寻找大量的图片做素材&…

CSS ::file-selector-button伪元素修改input上传文件按钮的样式

默认样式 修改后的样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdev…

【MySQL】查询进阶

查询进阶 数据库约束约束类型NULL , DEFAULT , UNIQUE 约束主键约束外键约束 聚合查询聚合函数group by子句HAVING 联合查询内连接外连接自连接子查询单行子查询多行子查询 数据库约束 约束类型 NOT NULL #表示某行不能储存空值 UNIQUE #保证每一行必须有唯一的值 DEFAULT #规…

CSS科技感四角边框

实现效果:使用before和after就可以实现,代码量不多,长度颜色都可以自己调整 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><style>*{margin:0;padding:0;}html,body{…

PostgreSQL MVCC的弊端

数据库有很多种&#xff08;截至 2023 年 4 月有 897 个&#xff09;。面对如此多的数据库&#xff0c;很难知道该选择什么&#xff01;但有一个有趣的现象&#xff0c;互联网集体决定新应用程序的默认选择。在 2000 年代&#xff0c;传统观点选择 MySQL 是因为像 Google 和 Fa…

vue3+vite+pinia+vue-router+ol项目创建及配置

一、vite (一)、定义 vite官网 (二)、操作步骤 注意&#xff1a;两种方式创建目录结构一致 方式一&#xff1a;vite创建脚手架命令&#xff1a; 命令行&#xff1a;npm create vitelatest 然后选择 方式二&#xff1a;命令行直接声明带上vue 二、pinia (一)、定义 定义&#xf…

快速批量改名文件!随机字母命名,让文件名更有创意!

想要让文件名更加有创意和个性化吗&#xff1f;不妨尝试使用随机字母来批量改名文件&#xff01;无论是照片、文档还是其他文件&#xff0c;只需要简单的几个步骤&#xff0c;您就可以为它们赋予一个独特的随机字母命名。这不仅可以帮助您整理文件&#xff0c;还能增加一些乐趣…

非50欧系统阻抗的S参数测试

1. S参数依赖于系统阻抗 S参数的定义需要约定一个系统阻抗。同一个微波电路&#xff0c;在不同系统阻抗下的S参数是不同的。例如&#xff0c;50欧电阻在50欧系统阻抗下的S11为零&#xff0c;是没有反射的匹配状态&#xff1b;但50欧电阻在75欧系统阻抗下的S11不为零&#xff0…

Orange:一个基于 Python 的数据挖掘可视化平台

本篇介绍一个适合初学者入门的机器学习工具。 Orange 简介 Orange 是一个开源的数据挖掘和机器学习软件。Orange 基于 Python 和 C/C 开发&#xff0c;提供了一系列的数据探索、可视化、预处理以及建模组件。 Orange 拥有漂亮直观的交互式用户界面&#xff0c;非常适合新手进…

计算机网络 day11 tcpdump - 传输层 - netstat - socket - nc - TCP/UDP头部

目录 故障排查 tcpdump抓包工具 传输层&#xff08;TCP和UDP协议&#xff09; 传输层的作用 应用程序和端口号有什么关系&#xff1f; 传输层端对端连接实现拓扑图 如何查看自己的linux机器开放了哪些端口&#xff1f; 1、netstat(network status 网络的状态) netsta…

【ceph】存储池pg个数如何设置

存储池pg个数如何设置 参考官方文档说明&#xff1a;https://old.ceph.com/pgcalc/参数说明TargePGs per OSD&#xff1a;每个OSD的pg数OSD#存储池包含osd个数%Data存储池写入数据占总OSD容量百分比Size存储池冗余数

【复盘】记录一次类型不一致导致的Kafka消费异常问题

背景 业务主要是通过A系统向B系统写入Kafka&#xff0c;然后B系统消费Kafka 将结果写到Kafka中&#xff0c;A进行消费最终结果。 在整个流程中&#xff0c;A写入Kafka会写入一张 record1表记录&#xff0c;然后在A消费最终结果的时候也记录一张record2表。主要改动的话 只是B系…

设计模式07-责任链模式

责任链模式属于行为设计模式&#xff0c;常见的过滤器链就是使用责任链模式设计的。 文章目录 1、真实开发场景的问题引入2、责任链模式讲解2.1 核心类及类图2.2 基本代码 3、利用构建者模式解决问题4、责任链模式的应用实例5、总结5.1 解决的问题5.2 使用场景5.3 优缺点 1、真…

MFC 基于数据库的管理系统

文章目录 初始化设置菜单 添加数据库类创建数据库配置数据库 全部代码 初始化 创建文件选择基于CListView 初始化数据 public:CListCtrl& m_list;CSQLView::CSQLView() noexcept:m_list(GetListCtrl()) {// TODO: 在此处添加构造代码}void CSQLView::OnInitialUpdate() {C…

数据结构与算法——什么是线性表(线性存储结构)

我们知道&#xff0c;具有“一对一”逻辑关系的数据&#xff0c;最佳的存储方式是使用线性表。那么&#xff0c;什么是线性表呢&#xff1f; 线性表&#xff0c;全名为线性存储结构。使用线性表存储数据的方式可以这样理解&#xff0c;即“把所有数据用一根线儿串起来&#xf…

餐饮业油烟在线监测系统的具体应用 安科瑞 许敏

摘要&#xff1a;本文利用物联网技术&#xff0c;构建了一套餐饮企业智能油烟在线监测系统&#xff0c;该系统前台由厨房端和管道端组成&#xff0c;通过网关接入云平台管理系统&#xff0c;实时监控烟道阀门的启闭、变频风机的启停与风速及功率调节、油烟浓度数据等。结合动态…

随手笔记——如何手写高斯牛顿法

随手笔记——如何手写高斯牛顿法 说明源代码 说明 将演示如何手写高斯牛顿法 源代码 #include <iostream> #include <chrono> #include <opencv2/opencv.hpp> #include <Eigen/Core> #include <Eigen/Dense>using namespace std; using names…

HBase

一 HBase简介与环境部署 1.1 HBase简介&在Hadoop生态中的地位 1.1.1 什么是HBase HBase是一个分布式的、面向列的开源数据库HBase是Google BigTable的开源实现HBase不同于一般的关系数据库, 适合非结构化数据存储 1.1.2 BigTable BigTable是Google设计的分布式数据存储…