2025 cs144 Lab Checkpoint 3: TCP Receiver

news2025/6/9 20:37:44

文章目录

  • 1 关于TCP Sender
    • 1.1 关键机制
      • 重传超时(RTO)与定时器
  • 2 实现TCP Sender
    • 2.1 void push( const TransmitFunction& transmit );
      • const TransmitFunction& transmit 函数型参数?
      • 从哪里读取字节?
      • input_里的数据是哪来的?
      • 实现
    • 2.2 void receive( const TCPReceiverMessage& msg );
      • 窗口大小是用来限制每次发送的段的大小,还是所有要发送的数据的大小?
      • 实现
    • 2.3 void tick( uint64 t ms since last tick, const TransmitFunction& transmit );
      • 实现
      • 谁来调用tick()?
    • TCPSenderMessage make empty message() const;
      • 实现
    • 几个疑问
      • TCP是单工通信还是双工通信?
      • 数据流是如何传输的

1 关于TCP Sender

核心职责:

  • 跟踪接收方窗口的ackno、window_size
  • 尽可能地填充窗口,直到窗口已满或发送完毕
  • 管理未确认的段、超时未确认则重传(ARQ机制
  • 处理SYN、FIN标志,确保流的正确开始和结束

1.1 关键机制

重传超时(RTO)与定时器

TCPSender 的 owner 会周期性地调用 TCPSender 的 tick 方法,表示时间的流逝。TCPSender 负责查看其未完成的 TCPSenderMessages 集合,并确定最早发送的 segment 是否在没有确认的情况下未完成太长时间(即,没有确认其所有序列号)。如果是这样,则需要重新传输 (再次发送) 。

2 实现TCP Sender

2.1 void push( const TransmitFunction& transmit );

要求 TCPSender 从出站字节流填充窗口:它从流中读取并发送尽可能多的 TCPSenderMessages,只要窗口中有要读取的新字节和可用空间。它通过调用提供的 transmit() 函数来发送它们。
您需要确保您发送的每个 TCPSenderMessage 都完全适合接收者的窗口。使每条消息尽可能大,但不要大于 TCPConfig::MAX PAYLOAD SIZE 给出的值。

const TransmitFunction& transmit 函数型参数?

跳转到定义:

  /* Type of the `transmit` function that the push and tick methods can use to send messages */
  using TransmitFunction = std::function<void( const TCPSenderMessage& )>;

作用是,当发送器生成一个待发送的段(如 SYN 段、数据段、FIN 段)时,通过调用 transmit(segment) 将段传递给框架,segment的类型是TCPSenderMessage

从哪里读取字节?

当然是从TCPSender类里自己的字节流input_里读取,此时,TCPSender作为发送方

input_里的数据是哪来的?

构造TCPSender类时放入的,构造TCPSender时,同时还会给定Initial_RTO和ISN

实现

void TCPSender::push(const TransmitFunction& transmit)
{
  // 首先检查Writer是否存在错误并设置错误状态,有错误的话停止push,并返回空的message
  if (writer().has_error()) {
    _has_error = true;
    cerr << "DEBUG: writer has error, setting _has_error = true" << endl;
  }

  if (_has_error) {
    cerr << "DEBUG: _has_error is true in push(), sending RST message" << endl;
    TCPSenderMessage rst_msg = make_empty_message();
    transmit(rst_msg);
    return;
  }
  
  // 如果没有错误,正常处理...
  // 如果可接收的窗口大小为0且没有要重传的消息,则设置窗口大小为1
  uint64_t effective_window = (received_msg.window_size == 0 && outstanding_bytes == 0) ? 1 : received_msg.window_size;
  
  //如果当前的窗口大小可以容纳待重传的消息,则处理数据
  while (outstanding_bytes < effective_window) {
    TCPSenderMessage msg;
    //发送SYN消息
    if (isSent_ISN == false) {
      msg.SYN = true;
      msg.seqno = isn_;
      isSent_ISN = true;  // 立即设置标志
    } else {
      msg.seqno = Wrap32::wrap(abs_seqno, isn_);
    }
    
    // 计算可用窗口大小(考虑已发送但未确认的字节)
    size_t remaining_window = effective_window - outstanding_bytes;
    // 如果是SYN消息,需要减去一个字节,因为SYN占用一个序列号
    if (msg.SYN) {
      remaining_window = remaining_window > 0 ? remaining_window - 1 : 0;
    }
    
    // 计算可以发送的数据大小
    size_t payload_size = min(remaining_window, TCPConfig::MAX_PAYLOAD_SIZE);
    payload_size = min(payload_size, writer().reader().bytes_buffered());
    
    // 读取数据
    read(writer().reader(), payload_size, msg.payload);
    
    // 修改FIN逻辑:只有当发送完所有数据后,且确保FIN的一个字节也能放入窗口时才添加FIN
    if (writer().is_closed() && !isSent_FIN && 
        writer().reader().bytes_buffered() == 0 && 
        outstanding_bytes + msg.sequence_length()  < effective_window) {
      isSent_FIN = true;
      msg.FIN = true;
    }
    
    if (!msg.sequence_length()) break;

    outstanding_collections.push_back(msg);
    outstanding_bytes += msg.sequence_length();  // 确保正确计算序列号占用
    abs_seqno += msg.sequence_length();
    
    // 立即发送创建的消息
    transmit(msg);
    
    // 如果有未确认的数据,启动计时器
    if (outstanding_bytes > 0 && !is_start_timer) {
      is_start_timer = true;
      cur_RTO_ms = initial_RTO_ms_;
    }
  }
}

2.2 void receive( const TCPReceiverMessage& msg );

从接收器接收一条消息,传达窗口的新左边缘 (= ackno) 和右边缘 (= ackno + 窗口大小)。TCPSender 应查看其未完成的 segment 集合,并删除任何现已完全确认的 segment(ackno 大于 segment 中的所有序列号)。
左边缘:告知发送方 “已确认的数据边界”,释放已处理的段。
右边缘:告知发送方 “可接收的数据边界”,限制发送速率和数据量。

窗口大小是用来限制每次发送的段的大小,还是所有要发送的数据的大小?

后者。因为之前window_size设置的是TCPReceiver的字节流可以接受的最大字节数量。
那段的大小是由谁限制来着?
《自顶向下》上说是MSS(最大报文长度)。

实现

void TCPSender::receive(const TCPReceiverMessage& msg)
{
  // 检查收到的RST标志
  if (msg.RST) {
    _has_error = true;
    // 还需要设置底层writer的错误状态
    const_cast<Writer&>(writer()).set_error();
    return;
  }

  if (_has_error) {
    return;  // 如果有错误,不执行任何操作
  }
  
  received_msg = msg;
  primitive_window_size = msg.window_size;
  if (msg.ackno.has_value() == true) {
    uint64_t ackno_unwrapped = static_cast<uint64_t>(msg.ackno.value().unwrap(isn_, abs_seqno));
    if (ackno_unwrapped > abs_seqno) return;
    while (outstanding_bytes != 0 && 
           static_cast<uint64_t>(outstanding_collections.front().seqno.unwrap(isn_, abs_seqno)) + 
           outstanding_collections.front().sequence_length() <= ackno_unwrapped) {
      outstanding_bytes -= outstanding_collections.front().sequence_length();
      outstanding_collections.pop_front();
      consecutive_retransmissions_nums = 0;
      cur_RTO_ms = initial_RTO_ms_;
      if (outstanding_bytes == 0) is_start_timer = false;
      else is_start_timer = true;
    }
  }
}

2.3 void tick( uint64 t ms since last tick, const TransmitFunction& transmit );

Time has passed — 自上次调用此方法以来的一定毫秒数。发送方可能需要重新传输未完成的段;它可以调用 transmit() 函数来执行此作。(提醒:请不要尝试在代码中使用实际的 “clock” 或 “gettimeofday” 函数;对时间传递的唯一引用来自 ms since last tick 参数。

实现

关键点:

  • 重传判断
  • 指数退避
void TCPSender::tick(uint64_t ms_since_last_tick, const TransmitFunction& transmit)
{
  if (_has_error) {
    return;  // 如果有错误,不执行任何操作
  }
  
  // 只有当有未确认的数据且计时器启动时才减少时间
  if (is_start_timer && outstanding_bytes > 0) {
    if (cur_RTO_ms <= ms_since_last_tick) {
      // 超时,重传第一个未确认的段
      transmit(outstanding_collections.front());
      consecutive_retransmissions_nums++;
      if (primitive_window_size) cur_RTO_ms = (1UL << consecutive_retransmissions_nums) * initial_RTO_ms_;
      else cur_RTO_ms = initial_RTO_ms_;
      
    } else {
      cur_RTO_ms -= ms_since_last_tick;
    }
  }
}

谁来调用tick()?

发送数据时,push中发现有需要重传的消息,就会启动定时器

if (outstanding_bytes > 0 && !is_start_timer) {
  is_start_timer = true;
  cur_RTO_ms = initial_RTO_ms_;
}

只是启动,不是调用,真正的调用tick是在测试框架的上层模块

TCPSenderMessage make empty message() const;

TCPSender 应生成并发送一条序列号设置正确的零长度消息。如果 Peer 节点想要发送 TCPReceiverMessage(例如,因为它需要确认来自 Peer 节点的发送者的某些内容)并且需要生成 TCPSenderMessage 来配合它,这将非常有用。

实现

注意RST的设置

TCPSenderMessage TCPSender::make_empty_message() const
{
  TCPSenderMessage msg;
  msg.seqno = Wrap32::wrap(abs_seqno, isn_);
  
  // 检查是否有错误,无论是来自内部标志还是Writer
  bool has_error = _has_error || writer().has_error();
  cerr << "DEBUG: make_empty_message called, _has_error = " << (_has_error ? "true" : "false") 
       << ", writer().has_error() = " << (writer().has_error() ? "true" : "false") << endl;
  
  if (has_error) {
    msg.RST = true;
  }
  
  return msg;
}

几个疑问

TCP是单工通信还是双工通信?

全双工,“一对流量控制的字节流”。
Sender和Recevier各自独立存在一个字节流,支持双向数据传输。

数据流是如何传输的

留个坑

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

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

相关文章

【笔记】解决MSYS2安装后cargo-install-update.exe-System Error

#工作记录 cargo-install-update.exe-System Error The code execution cannot proceed because libgit2-1.9.dll wasnot found. Reinstalling the program may fix this problem. …

银行卡二三四要素实名接口如何用PHP实现调用?

一、什么是银行卡二三四要素实名接口 输入银行卡卡号、姓名、身份证号码、手机号&#xff0c;验证此二三四要素是否一致。 二、核心价值 1. 提升风控效率 通过实时拦截冒用身份开户&#xff0c;银行卡二三四要素实名接口显著降低了人工审核成本&#xff0c;效率提升50%以上…

itvbox绿豆影视tvbox手机版影视APP源码分享搭建教程

我们先来看看今天的主题&#xff0c;tvbox手机版&#xff0c;然后再看看如何搭建&#xff1a; 很多爱好者都希望搭建自己的影视平台&#xff0c;那该如何搭建呢&#xff1f; 后端开发环境&#xff1a; 1.易如意后台管理优化版源码&#xff1b; 2.宝塔面板&#xff1b; 3.ph…

网页抓取混淆与嵌套数据处理流程

当我们在网页抓取中&#xff0c;遇到混淆和多层嵌套的情况是比较常见的挑战。混淆大部分都是为了防止爬虫而设计的&#xff0c;例如使用JavaScript动态加载、数据加密、字符替换、CSS偏移等。多层嵌套则可能是指HTML结构复杂&#xff0c;数据隐藏在多层标签或者多个iframe中。 …

高性能MYSQL:复制同步的问题和解决方案

一、复制的问题和解决方案 中断MySQL的复制并不是件难事。因为实现简单&#xff0c;配置相当容易&#xff0c;但也意味着有很多方式会导致复制停止&#xff0c;陷入混乱并中断。 &#xff08;一&#xff09;数据损坏或丢失的错误 由于各种各样的原因&#xff0c;MySQL 的复制…

大话软工笔记—架构模型

1. 架构模型1—拓扑图 &#xff08;1&#xff09;拓扑图概念 拓扑图&#xff0c;将多个软件系统用网络图连接起来的表达方式。 &#xff08;2&#xff09;拓扑图分类 总线型结构 比较普遍采用的方式&#xff0c;将所有的系统接到一条总线上。 星状结构 各个系统通过点到…

javaweb -html -CSS

HTML是一种超文本标记语言 超文本&#xff1a;超过了文本的限制&#xff0c;比普通文本更强大&#xff0c;除了文字信息&#xff0c;还可以定义图片、音频、视频等内容。 标记语言&#xff1a;由标签"<标签名>"构成的语言。 CSS:层叠样式表&#xff0c;用于…

spring task定时任务快速入门

spring task它基于注解和配置&#xff0c;可以轻松实现任务的周期性调度、延迟执行或固定频率触发。按照我们约定的时间自动执行某段代码。例如闹钟 使用场景 每月还款提醒&#xff0c;未支付的订单自动过期&#xff0c;收到快递后自动收货&#xff0c;系统自动祝你生日快乐等…

搭建nginx的负载均衡

1、编写一个configMap的配置文件 events {worker_connections 1024; # 定义每个worker进程的最大连接数 }http {# 定义通用代理参数&#xff08;替代proxy_params文件&#xff09;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-F…

Appium+python自动化(八)- 认识Appium- 下章

1、界面认识 在之前安装appium的时候说过我们有两种方法安装&#xff0c;也就有两种结果&#xff0c;一种是有界面的&#xff08;客户端安装&#xff09;&#xff0c;一种是没有界面的&#xff08;终端安装&#xff09;&#xff0c;首先我们先讲一下有界面的&#xff0c;以及界…

LabVIEW的MathScript Node 绘图功能

该VI 借助 LabVIEW 的 MathScript Node&#xff0c;结合事件监听机制&#xff0c;实现基于 MathScript 的绘图功能&#xff0c;并支持通过交互控件自定义绘图属性。利用 MathScript 编写脚本完成图形初始化&#xff0c;再通过LabVIEW 事件结构响应用户操作&#xff0c;动态修改…

每日Prompt:治愈动漫插画

提示词 现代都市治愈动漫插画风格&#xff0c;现代女子&#xff0c;漂亮&#xff0c;长直发&#xff0c;20岁&#xff0c;豆沙唇&#xff0c;白皙&#xff0c;气质&#xff0c;清纯现代都市背景下&#xff0c;夕阳西下&#xff0c;一位穿着白色露脐短袖&#xff0c;粉色工装裤…

6.8 note

paxos算法_初步感知 Paxos算法保证一致性主要通过以下几个关键步骤和机制&#xff1a; 准备阶段 - 提议者向所有接受者发送准备请求&#xff0c;请求中包含一个唯一的编号。 - 接受者收到请求后&#xff0c;会检查编号&#xff0c;如果编号比它之前见过的都大&#xff0c;就会承…

面试心得 --- 车载诊断测试常见的一些面试问题

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

跟进一下目前最新的大数据技术

搭建最新平台 40C64G服务器&#xff0c;搭建3节点kvm&#xff0c;8C12G。 apache-hive-4.0.1-bin apache-tez-0.10.4-bin flink-1.20.1 hadoop-3.4.1 hbase-2.6.2 jdk-11.0.276 jdk8u452-b09 jdk8终于可以不用了 spark-3.5.5-bin-hadoop3 zookeeper-3.9.3 trino…

系统模块与功能设计框架

系统模块与功能设计框架&#xff0c;严格遵循专业架构设计原则&#xff0c;基于行业标准&#xff08;如微服务架构、DDD领域驱动设计&#xff09;构建。设计采用分层解耦模式&#xff0c;确保可扩展性和可维护性&#xff0c;适用于电商、企业服务、数字平台等中大型系统。 系统…

我爱学算法之—— 前缀和(中)

一、724. 寻找数组的中心下标 题目解析 这道题&#xff0c;给定数组nums&#xff0c;要求我们找出这个数组的中心下标。 **中心下标&#xff1a;**指左侧所有元素的和等于右侧所有元素的和。 如果存在多个中心数组下标&#xff0c;就返回最左侧的中心数组下标。 算法思路 暴…

Elasticsearch从安装到实战、kibana安装以及自定义IK分词器/集成整合SpringBoot详细的教程ES(三)

DSL官方地址&#xff1a; DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;https://www.elastic.co/docs/explore-analyze/query-filter/languages/querydsl&#xff09;来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有数据&#xff0…

React Hooks 指南:何时使用 useEffect ?

在 React 的函数组件中&#xff0c;useEffect Hook 是一个强大且不可或缺的工具。它允许我们处理副作用 (side effects)——那些在组件渲染之外发生的操作。但是&#xff0c;什么时候才是使用 useEffect 的正确时机呢&#xff1f;让我们深入探讨一下&#xff01; 什么是副作用…

API标准的本质与演进:从 REST 架构到 AI 服务集成

在当今数字化浪潮中&#xff0c;API 已成为系统之间沟通与协作的“语言”&#xff0c;REST&#xff08;Representational State Transfer&#xff0c;表述性状态转移&#xff09;是一种基于 HTTP 协议的 Web 架构风格。它不仅改变了 Web 应用开发的方式&#xff0c;也成为构建现…