CS144 计算机网络实验 lab3 笔记

CS144 计算机网络实验 lab3 笔记

介绍

本实验中,我们将会在之前实验的基础上,实现一个TCP sender ----将字节流转换成数据报并发送.

image-20220124144804905

TCP协议是一个在不可靠的协议上提供可靠的,流量控制的协议。

我们在本实验中会实现一个TCP发送端,负责将发送端应用层传入的比特流转换成一系列由发出的TCP报文段,在另一端,由TCP接收端将TCP报文段转换成原始比特流(也有可能什么都不做,在从未受到syn,或者多次受到的时候),并将ack窗口大小返回给TCP发送端

TCP接收端和发送端都负责处理一部分TCP报文段。TCP发送端写入到报文中的每部分都会被TCP接收端解析,包括:seq,SYN,FIN,内容

但是,TCP发送端只会一部分报文的内容,包括:acknoSYN

image-20220124155145910

TCP发送端有以下任务:

  • 跟踪window size,也就是处理acknowindow sizes
  • 尽可能的填充 ( 自己的 ) windows,直到满了或者ByteStream空了为止
  • 对已经发送但没有收到ackno的报文段 ( 未被确认的报文段) 保持跟踪
  • 如果超出规定的时间,还没有收到对应的ack,就重新发送所有已经发送,但没有收到ackno的报文段

TCP发送端是如何知道报文段丢失呢?

TCP发送端会发送一系列的报文段. 每个报文段都是来自ByteStream的子串加上位置索引seq,如果是第一个报文段,需要加上syn,最后一个需要加上fin

为了发送这些报文段,TCP发送端会对所有已发送的报文段保持跟踪,直到收到响应报文段的ack

具体实现:

  1. TCP发送端会定期的调用TCPSender::tick函数,来表明时间的流逝.
  2. TCP发送端负责监管已发出报文段,如果最早的已发出但没有收到ack的报文段,超出了规定时间,它将会被重新发送

实现记录时间,以及计算时间

  1. 每隔几毫秒,TCP发送端的TCPSender’s tick方法将被调用,它告自上次调用此方法以来已经过多少毫秒,不需要自己处理时间
  2. 当TCP发送端初始化的时候,将会有一个retransmission timeout (RTO),这就是重传时间,重传时间是变化的,但是最初的超时时间都是一样的,需要调用_initial retransmission timeout
  3. 实现一个重传定时器: 当重传时间到达时发出警告,超过重传时间关闭警告,只可以依赖tick方法,不可以根据现实时间
  4. 每次一个数据报发送的时候,如果定时器没有开启,就要开启定时器
  5. 当确认所有数据时,停止重传计时器
  6. 如果调用tick后发现定时器过期:
    • 重传最早的未收到ack的包
    • 如果windows size大小不为零:
      • 你需要跟踪连续重传的数量,因为TCP连接需要据此判断连接是否出现问题,是否需要中断
      • 将重传定时器时长加倍,这叫做exponential backoff指数补偿,随着重传次数的增加,补偿的程度也会指数增长
    • 在超过重传时间后重置重传定时器并开启
  7. 当成功收到数据
    • 设置重传时间为初始值
    • 如果发送重传数据,就需要重启定时器
    • 将连续重传记录的数量置零

实现TCP发送端 Implementing the TCP sender

思路

  1. void fill_window()
    • TCP发送端被要求尽可能的读取ByteStream中的数据,并且形成数据报 ( 未发送 )
    • 确保每次发送的数据报的量正好等于TCP接收端窗口的大小
    • 使用TCPSegment::length in sequence space()得到seq,不要忘了SYN , FIN 也占用一个序列号,所以也占用窗口空间
  2. void ack_received(const WrappingInt32 ackno, const uint16 t window size)
    • 得到接收端TCP窗口的左沿和大小 , ackno理应大于所有已发送数据报的seq
  3. void tick( const size t ms since last tick )
    • 表示自从上一次发送后经过多少毫秒了.不需要我们来调用,参数的意义是距离上次调用经过了多少时间,这个不需要我们操心,我们需要实现每次调用tick()的时候函数做什么
  4. void send empty segment()
    • 生成空的数据报,只发一个ack

image-20220124211130547

思路总结

  1. void tick( const size t ms since last tick )
    • 表示自从上一次发送后经过多少毫秒了.不需要我们来调用,参数的意义是距离上次调用经过了多少时间,这个不需要我们操心,我们需要实现每次调用tick()的时候函数做什么
  2. 我,们需要存储已发送但未被确认的报文段,进行累计确认,如果超时,只需要重传最早的报文段即可
  3. TCPReceiver 调用 unwrap 时的 checkpoint 是上一个接收到的报文段的 absolute_seqnoTCPSender 调用unwrap时的 checkpoint_next_seqno
  4. retransmission timeout(RTO),具体实现是RFC6298的简化版
    • 重传连续的之后double ;收到ackno后重置到_initial_RTO
    • 可参考RFC 6298第5小节实现_timer

加入成员变量

private:bool _syn_sent = false;bool _fin_sent = false;uint64_t _bytes_in_flight = 0;  // Number of bytes in flight 就是未被确认的字节数uint16_t _receiver_window_size = 0;  //接收方的滑动窗口uint16_t _receiver_free_space = 0; //接收方的剩余空间uint16_t _consecutive_retransmissions = 0; //unsigned int _time_elapsed = 0;bool _timer_running = false;                    std::queue<TCPSegment> _segments_outstanding{};  // the segment has been sentvoid send_segment(TCPSegment &seg);              // send segmentbool _ack_valid(uint64_t abs_ackno);//! our initial sequence number, the number for our SYN.WrappingInt32 _isn;//! outbound queue of segments that the TCPSender wants sentstd::queue<TCPSegment> _segments_out{};//! retransmission timer for the connectionunsigned int _initial_retransmission_timeout;//! outgoing stream of bytes that have not yet been sentByteStream _stream;//! the (absolute) sequence number for the next byte to be sentuint64_t _next_seqno{0};unsigned int _rto = 0;
public:

函数实现

fill_window() 实现

  • 如果syn未发送,发送并返回
  • 如果syn未应答,返回
  • 如果fin已发送,返回
  • 如果 _receiver_window_size 不为 0
    • 当 receiver_free_space 不为 0,尽可能地填充 payload
    • 如果 _stream 已经 EOF,且 _receiver_free_space 仍不为 0,填上 FIN(fin 也会占用 _receiver_free_space)
    • 如果 _receiver_free_space 还不为 0,且 _stream 还有内容,回到步骤 1 继续填充
  • 如果 _receiver_window_size 为 0,则需要发送零窗口探测报文
    • 如果 _receiver_free_space 为 0
      • 如果 _stream 已经 EOF,发送仅携带 FIN 的报文
      • 如果 _stream 还有内容,发送仅携带一位数据的报文

send_segment()实现

  • 发送seq,不要忘记isn
  • 记录接收方窗口大小,如果syn true,减去发送包的大小
  • 如果计时器未开启,开启计时器

其他的都很好理解,看一下代码就能懂

总体来说,有点小复杂,总有一些地方不明了,最后借鉴了一位博主的实现成功完成

#include "tcp_sender.hh"#include "tcp_config.hh"#include <random>
#include <algorithm>
// Dummy implementation of a TCP sender// For Lab 3, please replace with a real implementation that passes the
// automated checks run by `make check_lab3`.template <typename... Targs>
void DUMMY_CODE(Targs &&.../* unused */) {}using namespace std;//! \param[in] capacity the capacity of the outgoing byte stream
//! \param[in] retx_timeout the initial amount of time to wait before retransmitting the oldest outstanding segment
//! \param[in] fixed_isn the Initial Sequence Number to use, if set (otherwise uses a random ISN)
TCPSender::TCPSender(const size_t capacity, const uint16_t retx_timeout, const std::optional<WrappingInt32> fixed_isn): _isn(fixed_isn.value_or(WrappingInt32{random_device()()})), _initial_retransmission_timeout{retx_timeout}, _stream(capacity), _rto{retx_timeout} {}
uint64_t TCPSender::bytes_in_flight() const { return _bytes_in_flight; }void TCPSender::fill_window() {if (!_syn_sent) {_syn_sent = true;TCPSegment seg;seg.header().syn = true;send_segment(seg);return;}if (!_segments_outstanding.empty() && _segments_outstanding.front().header().syn)return;if (!_stream.buffer_size() && !_stream.eof())return;if (_fin_sent)return;if (_receiver_window_size) {while (_receiver_free_space) {TCPSegment seg;size_t payload_size = min({_stream.buffer_size(),static_cast<size_t>(_receiver_free_space),static_cast<size_t>(TCPConfig::MAX_PAYLOAD_SIZE)});seg.payload() = _stream.read(payload_size);if (_stream.eof() && static_cast<size_t>(_receiver_free_space) > payload_size) {seg.header().fin = true;_fin_sent = true;}send_segment(seg);if (_stream.buffer_empty())break;}} else if (_receiver_free_space == 0) {// The zero-window-detect-segment should only be sent once (retransmition excute by tick function).// Before it is sent, _receiver_free_space is zero. Then it will be -1.TCPSegment seg;if (_stream.eof()) {seg.header().fin = true;_fin_sent = true;send_segment(seg);} else if (!_stream.buffer_empty()) {seg.payload() = _stream.read(1);send_segment(seg);}}
}void TCPSender::send_segment(TCPSegment &seg) {seg.header().seqno = wrap(_next_seqno, _isn);_next_seqno += seg.length_in_sequence_space();_bytes_in_flight += seg.length_in_sequence_space();if (_syn_sent)_receiver_free_space -= seg.length_in_sequence_space();_segments_out.push(seg);_segments_outstanding.push(seg);if (!_timer_running) {_timer_running = true;_time_elapsed = 0;}
}
// See test code send_window.cc line 113 why the commented code is wrong.
//! \param ackno The remote receiver's ackno (acknowledgment number)
//! \param window_size The remote receiver's advertised window size
void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) {uint64_t abs_ackno = unwrap(ackno, _isn, _next_seqno);if (!_ack_valid(abs_ackno)) {// cout << "invalid ackno!\n";return;}_receiver_window_size = window_size;_receiver_free_space = window_size;while (!_segments_outstanding.empty()) {TCPSegment seg = _segments_outstanding.front();if (unwrap(seg.header().seqno, _isn, _next_seqno) + seg.length_in_sequence_space() <= abs_ackno) {_bytes_in_flight -= seg.length_in_sequence_space();_segments_outstanding.pop();// Do not do the following operations outside while loop.// Because if the ack is not corresponding to any segment in the segment_outstanding,// we should not restart the timer._time_elapsed = 0;_rto = _initial_retransmission_timeout;_consecutive_retransmissions = 0;} else {break;}}if (!_segments_outstanding.empty()) {_receiver_free_space = static_cast<uint16_t>(abs_ackno + static_cast<uint64_t>(window_size) -unwrap(_segments_outstanding.front().header().seqno, _isn, _next_seqno) - _bytes_in_flight);}if (!_bytes_in_flight)_timer_running = false;// Note that test code will call it again.fill_window();
}// See test code send_window.cc line 113 why the commented code is wrong.
bool TCPSender::_ack_valid(uint64_t abs_ackno) {return abs_ackno <= _next_seqno &&//  abs_ackno >= unwrap(_segments_outstanding.front().header().seqno, _isn, _next_seqno) +//          _segments_outstanding.front().length_in_sequence_space();abs_ackno >= unwrap(_segments_outstanding.front().header().seqno, _isn, _next_seqno);
}
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void TCPSender::tick(const size_t ms_since_last_tick) {if (!_timer_running)return;_time_elapsed += ms_since_last_tick;if (_time_elapsed >= _rto) {_segments_out.push(_segments_outstanding.front());if (_receiver_window_size || _segments_outstanding.front().header().syn) {++_consecutive_retransmissions;_rto <<= 1;}_time_elapsed = 0;}
}unsigned int TCPSender::consecutive_retransmissions() const { return _consecutive_retransmissions; }void TCPSender::send_empty_segment() {TCPSegment seg;seg.header().seqno = wrap(_next_seqno, _isn);_segments_out.push(seg);
}

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

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

相关文章

CS144 lab4 计算机网络实验 笔记

CS144 lab4 计算机网络实验 笔记 介绍 本实验中,我们将组合TCP sender和TCP receiver实现一个完整的TCP connection TCP是全双工连接,所以两方可以同时接收/发送信息,一端随时都有可能接收.发送信息 主要根据上图实现 tcp_connection.cc #include "tcp_connection.hh&…

C# GDI+ 实现图片分隔

1. 概述 有时候我们需要在web页面上显示一张图&#xff0c;比如说一张地图&#xff0c;而这张地图会比较大。这时候如果我们把一张大图分隔成一组小图&#xff0c;那么客户端的显示速度会明显地感觉块。希望阅读本文对你有所帮助。 2. 实现思路 .NET Framework GDI 为我们提…

c/c++面试试题(一)

1.求下面函数的返回值&#xff08;微软&#xff09;int func(x) { int countx 0; while(x) { countx ; x x&(x-1); } return countx; } 假定x 9999。 答案&#xff1a;8思路&#xff1a;将x转化为2进制&#xff0c;看含有的1…

2. Get the codes from GIT

Clone the code from git. Click the “GitEx Clone”. Paste the url into the “Repository to clone”. You can get the route from git repository from it: https://msstash.companydomainname.com/ .Find the project which you want to download and then click the “…

The Ranges Library (2) --- C++20

The Ranges Library (2) — C20 比较std与std::ranges算法 比较一下std::sort和std::ranges::sort std::sort template< class RandomIt > constexpr void sort( RandomIt first, RandomIt last );template< class ExecutionPolicy, class RandomIt > void sor…

WPF中的动画

WPF中的动画 周银辉动画无疑是WPF中最吸引人的特色之一&#xff0c;其可以像Flash一样平滑地播放并与程序逻辑进行很好的交互。这里我们讨论一下故事板。在WPF中我们采用Storyboard&#xf…

[访问系统] Api_Win32_Mac类工具包 (转载)

点击下载 Api_Win32_Mac.zip using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices;namespace BaseFunction {class API{[DllImport("kernel32")]//内存public static extern void GlobalM…

constexpr 函数

constexpr 函数 — C 20 constexpr double pi 3.14;constexpr允许你在编译时使用典型的C函数语法进行编程,但这并不意味之constexpr只和编译期有关 constexpr函数可以在编译期运行,也可以在运行时运行 但在以下情况constexpr函数必须在编译期运行: constexpr函数在编译的上…

constexpr和consteval --- C++ 20

constexpr和consteval — C 20 标准库容器和算法库对constexpr 的应用 C20 中大量的算法和容器可以使用constexpr,这意味着你甚至可以再编译期vector<int>进行排序 Algorithms library - cppreference.com 如下: #include <iostream> #include <ranges>…

函数模板(参考《C++ Templates 英文版第二版》)

函数编程(参考《C Templates 英文版第二版》) Chapter 1 函数模板 1.1 一窥函数模板 template<class T> T max(T a, T b) {return b < a ? a : b; }#include "max1.hpp" #include <iostream> #include <string> #include <format>int…

Curiously Recurring Template Pattern奇怪的模板递归 --- C++20

Curiously Recurring Template Pattern 奇怪的模板递归 — C20 我们都知道C有静态多态和动态多态,动态多态通过虚函数表实现,他的缺点就是对效率产生一点点影响 可以用CRTP解决这个问题 我们先举一个动态多态的例子: #include <iostream> using namespace std;class …

PROJECT #0 - C++ PRIMER [CMU 15-445645]笔记

PROJECT #0 - C PRIMER [CMU 15-445/645]笔记 这是数据库领域的一门课程, 由卡内基梅隆大学副教授Andy Pavlo授课, 目前在网上有授课视频资料、实验以及配套的在线测评环境 (限时开放至2021年12月31日) 环境: wsl2 Clion Project #0 - C Primer 还是很简单的,主要目的是让…

简单JS实现对表的行的增删

这段代码非常的简单&#xff0c;仅仅作为自己的一个小小的记录&#xff01; ok&#xff0c;先上一个简单的图例&#xff0c;效果如下&#xff08;注意&#xff1a;这只是一个简单的例子&#xff0c;不过可以根据这个简单的例子&#xff0c;变化出更为复杂的效果&#xff09;&am…