CS144 lab1 TCPReassembler学习笔记

 概述

    在实验 0 中,您使用 Internet 流套接字从网站获取信息并发送电子邮件,使用 Linux 的传输控制的内置实现协议 (TCP)。这个 TCP 实现设法产生了一对可靠的有序字节流(一个从你到服务器,一个在相反的方向),即使底层网络只提供“尽力而为”的数据报。我们的意思是:可以丢失、重新排序、更改或复制的数据包。您还实施了自己的字节流抽象,在一台计算机的内存中。在接下来的四个星期里,您将实现 TCP,以提供一对计算机之间的字节流抽象由不可靠的数据报网络分隔。
  我为什么要这样做?在不同的基础上提供服务或抽象不太可靠的服务是网络中许多有趣问题的原因。结束在过去的 40 年里,研究人员和从业者已经找到了如何传达各种事物——消息和电子邮件、超链接文档、搜索引擎、声音和视频、虚拟世界、协作文件共享、数字货币——通过互联网。TCP 本身的作用,使用不可靠的数据报提供一对可靠的字节流,是这方面的经典例子之一。一个合理的观点认为 TCP 实现算作地球上使用最广泛的非平凡计算机程序。
实验室作业将要求您以模块化方式构建 TCP 实现。
 

CS144:计算机网络简介

1. 在实验 1 中,您将实现一个流重组器——一个拼接小块的模块字节流(称为子串,或段)返回到连续流正确顺序的字节数。
2. 在实验 2 中,您将实现处理入站字节流的 TCP 部分:TCP接收器。这涉及考虑 TCP 将如何表示每个字节的位置在流中 - 称为“序列号”。TCPReceiver 负责告诉发送者 (a) 它能够组装多少入站字节流成功(这称为“确认”)和(b)发送方还有多少字节允许立即发送(“流量控制”)。
3. 在实验 3 中,您将实现处理出站字节流的 TCP 部分:TCP发送器。当发送方怀疑它传输的一个段时,它应该如何反应一路上迷路了,从未到达接收器?什么时候再试一次并重新传输丢失的段?
4. 在实验 4 中,您将把之前的工作与实验结合起来,创建一个工作TCP 实现:一个包含 TCPSender 和 TCPReceiver 的 TCPConnection。您将使用它与世界各地的真实服务器对话。
 

将子串按顺序排列

     在本实验和下一个实验中,您将实现一个 TCP 接收器:接收数据报并将它们转换为可靠的字节流以供用户读取(就像您的webget 程序从实验 0 中的 webserver 读取字节流)。
TCP 发送方将其字节流分成短段(子串不超过每个大约 1,460 字节),以便它们每个都适合数据报。但是网络可能重新排序这些数据报,或者丢弃它们,或者多次传送它们。接收者必须将这些段重新组合成它们开始时的连续字节流。
     在本实验中,您将编写负责此重组的数据结构:流重组器。它将接收子串(由一串字节和索引组成较大流中字符串的第一个字节)并提供一个 ByteStream 与所有数据正确排序。
界面如下所示:
// 构造一个 `StreamReassembler`,最多可以存储 `capacity` 个字节。
StreamReassembler( const size_t capcity);
// 接收一个子字符串并将任何新的连续字节写入流中。
//
// `data`: 段
// `index` 表示 `data` 中第一个字节的索引(按顺序放置)
// `eof`:该段的最后一个字节是整个流中的最后一个字节
void push_substring ( const string & data, const uint64_t index, const bool eof);
// 访问重组的字节流
const ByteStream stream_out();
// 已存储但尚未重组的子串中的字节数
size_t unassembled_bytes () const ;
// 内部状态是否为空(输出流除外)
bool empty() const ;

 

重组器的完整(公共)接口由 StreamReassembler 类描述在流 reassembler.hh 标头中。你的任务是实现这个类。您可以添加您希望 StreamReassembler 类的任何私有成员和成员函数,但是你不能改变它的公共接口。
⋆我为什么要这样做?
因为它是 TCP 对段的健壮性的核心重新排序,它将使处理传入的段变得更加容易。

常见问题

• 整个流中第一个字节的索引是多少?零。
• 我的实施应该有多高效?我们不会指定一个特定的还没有效率的概念,但请不要将其视为构建粗犷空间的挑战-或时间效率低下的数据结构——这种数据结构将成为你的基础TCP 实现。
• 如何处理不一致的子串?你可能认为他们没有存在。也就是说,您可以假设存在唯一的底层字节流,并且所有子字符串是它的(准确)切片。
• 我可以使用什么?您可以使用标准库的任何您觉得有用的部分。特别是,我们希望您至少使用一种数据结构。
• 什么时候应该将字节写入流?尽快。唯一的情况在哪个字节不应该在流中是当它之前有一个字节时还没有被“推”过。
• 子串可以重叠吗?是的。
• 我是否需要向StreamReassembler 添加私有成员?是的。由于字串可能以任何顺序到达,您的数据结构将必须“记住”子字符串,直到它们准备好放入流中——也就是说,直到它们之前的所有索引都被填满。

实验结果

实验总结

   这个实验主要是利用lab0写的读写字节流,完成一个字节重组器,对不按序到达的字串重组,排序好后读入字节流中,测试程序会自动读取字节流中的数据.

 

 

根据实验指示图,我们需要写一个数据结构,维持容量为capcity,绿色部分代表已经读入字节流但是还没读出的部分,红色部分代表还没有重组的部分,蓝色部分表示已经从字节流中读出的部分。

我们接受到data后,将不能重组的部分存储起来,如果能重组则直接重组,然后直接读入字节流。

不用担心读入失败问题,因为初始化时设置字节流的最大容量capcity和我们的数据结构维持的width是一样的,即绿色的部分永远小于width,所以只要能放进width的部分就可以直接读入字节流.

接受到data时,可能有超界的情况,比如:

 

 此时我们需要去掉两端多余的部分,保证data在不越界。

蓝色部分已经从字节流中读出,绿色部分也读入了字节流,我们实际需要储存的就只有红色部分,即未重组的子字符串

而每次push子字符串的时候需要重组,重组就要找到该子字符串附近能够重组的子字符串进行重组,于是想到了用set的upper_bound去快速查找

我们用set,自定义一个子字符串结构体node,然后利用其数据的start_index作为排序依据,然后每次push子字符串s1时,

先进行重组操作

用upper_bound找到start_index比它小(迭代器--)的另一个子字符串s2,然后向后一个一个查询,如果与s1可以重组,则将在s2的多出部分添加到s1,并从set中删除s2,最后将s2添加到set中

再进行读入判断

set是按照startindex排序的,所以我们只需要判断第一个节点的start_index是否为目前下一个需要读入到字节流的index,即图中的first_unressmbled,如果是,则表示可以读入,将第一个节点数据读入字节流,然后从set中删除该节点即可

eof判断

创建一个eof_index标记,当传入参数eof为true时,我们设置eof_index为data.size()+index,然后再每次数据读入字节流的时候判断是否读入数据已经到达eof_index,到达了就调用字节流的end_input函数结束读入。

stream_reassembler.hh

#ifndef SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#define SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH

#include "byte_stream.hh"

#include <cstdint>
#include <string>
#include <set>

//! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order,
//! possibly overlapping) into an in-order byte stream.
class StreamReassembler {
  private:
    // Your code here -- add private members as necessary.
    struct node{
      size_t start_idx=0;
      size_t end_idx=0;
      std::string data="";
      node(size_t index,const std::string &DATA){
        this->start_idx=index;
        this->data=DATA;
        this->end_idx=index+DATA.size()-1;
      }
      bool operator<(const node &b)const{
        if(start_idx!=b.start_idx)return start_idx<b.start_idx;
        else return end_idx<b.end_idx;
      }
    };
    ByteStream _outputStream;  //!< The reassembled in-order byte stream
    size_t _capacity=0;    //!< The maximum number of bytes
    std::set<node>_seg_buffer={};
    size_t _input_end_idx=UINT64_MAX;
    size_t _cur_idx=0;
    size_t _unassembled_bytes=0;
  private:
    void mergeTo(const node&a,node &b);
  public:
    //! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes.
    //! \note This capacity limits both the bytes that have been reassembled,
    //! and those that have not yet been reassembled.
    StreamReassembler(const size_t capacity);

    //! \brief Receive a substring and write any newly contiguous bytes into the stream.
    //!
    //! The StreamReassembler will stay within the memory limits of the `capacity`.
    //! Bytes that would exceed the capacity are silently discarded.
    //!
    //! \param data the substring
    //! \param index indicates the index (place in sequence) of the first byte in `data`
    //! \param eof the last byte of `data` will be the last byte in the entire stream
    void push_substring(const std::string &data, const uint64_t index, const bool eof);

    //! \name Access the reassembled byte stream
    //!@{
    const ByteStream &stream_out() const { return _outputStream; }
    ByteStream &stream_out() { return _outputStream; }
    //!@}

    //! The number of bytes in the substrings stored but not yet reassembled
    //!
    //! \note If the byte at a particular index has been pushed more than once, it
    //! should only be counted once for the purpose of this function.
    size_t unassembled_bytes() const;

    //! \brief Is the internal state empty (other than the output stream)?
    //! \returns `true` if no substrings are waiting to be assembled
    bool empty() const;
};

#endif  // SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH

 

stream_reassembler.cc

#include "stream_reassembler.hh"
#include<iostream>

// Dummy implementation of a stream reassembler.

// For Lab 1, please replace with a real implementation that passes the
// automated checks run by `make check_lab1`.

// You will need to add private members to the class declaration in `stream_reassembler.hh`


using namespace std;

StreamReassembler::StreamReassembler(const size_t capacity) : _outputStream(capacity), _capacity(capacity) {}

//! \details This function accepts a substring (aka a segment) of bytes,
//! possibly out-of-order, from the logical stream, and assembles any newly
//! contiguous substrings and writes them into the output stream in order.
void StreamReassembler::mergeTo(const node &a,node &b){
    if(a.start_idx<b.start_idx){
      b.data.insert(b.data.begin(),a.data.begin(),a.data.begin()+b.start_idx-a.start_idx);
      b.start_idx=a.start_idx;
    }
    if(a.end_idx>b.end_idx){
      b.data.insert(b.data.end(),a.data.end()-(a.end_idx-b.end_idx),a.data.end());
      b.end_idx=a.end_idx;
    }
}
//先重组,再提交到缓冲区
void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
    
    size_t max_idx=_cur_idx-_outputStream.buffer_size()+_capacity-1;
    if(eof){_input_end_idx=index+data.size();}
    node nd(index,data);

    //边界情况
    //如果已进入缓冲区的seg包含了该seg,或者该seg的起始idx都大于窗口最大值,或者seg为空直接丢弃
    if(_cur_idx>nd.end_idx||nd.start_idx>max_idx||data.empty()){
        //可能是eof延迟标记   
        if(_cur_idx>=_input_end_idx)_outputStream.end_input();
         return ;
    }
    //去除左右越界数据
    if(nd.start_idx<_cur_idx){
        nd.data=data.substr(_cur_idx-nd.start_idx);
        nd.start_idx=_cur_idx;
    }
    if(nd.end_idx>max_idx){
        nd.data=nd.data.substr(0,nd.data.size()-(nd.end_idx-max_idx));
        nd.end_idx=max_idx;
    }
    //处理完边界情况,当前seg的data都在nextIdx和maxIdx之间,把可以重合的seg重组
    if(!_seg_buffer.empty()){
       set<node>::iterator it=_seg_buffer.upper_bound(nd);
       if(it!=_seg_buffer.begin()){
           it--;
       }
       while(it!=_seg_buffer.end()){
        //如果有交集,则删除迭代器,重合部分添加在nd中,别问我为什么判断要写这么丑,直接放里面不行嘛,嗯,你可以试试
        int a=it->end_idx-(nd.start_idx-1),b=(nd.end_idx+1)-it->start_idx;
           if(a>=0&&b>=0){
              mergeTo(*it,nd);
              _unassembled_bytes-=it->data.size();
              it=_seg_buffer.erase(it);
           }
           //没有交集,但是seg已经在右侧,再往右找也找不到能产生交集的seg,退出
           else if(it->start_idx>nd.end_idx){
               break;
           }else{
               it++;
           }
       }
    }
    //提交到缓冲区,找seg最前的部分,判断是否能读入字节流
    if(nd.start_idx==_cur_idx){
        _outputStream.write(nd.data);
        _cur_idx=nd.end_idx+1;
        if(_cur_idx>=_input_end_idx)_outputStream.end_input();
    }else{
    _seg_buffer.insert(nd);
    _unassembled_bytes+=nd.data.size();
    }
}

size_t StreamReassembler::unassembled_bytes() const { return _unassembled_bytes; }

bool StreamReassembler::empty() const { return _unassembled_bytes==0; }

 

 

 

 

 

 

 

 

 

 

 
 
posted @ 2021-08-22 23:41  cono奇犽哒  阅读(550)  评论(0编辑  收藏  举报