CS144 lab2 TCPReceiver实现笔记

文档传送门

3 实现TCP接收器

在本实验的其余部分,您将实现 TCPReceiver。它将 (1) 从其对等方接收Segment,(2)使用您的 StreamReassembler 重新组装 ByteStream,并计算 (3) 确认号 (ackno) 和 (4) 窗口大小。
首先,请查看 TCP 段的格式。这是数据报的结构两个端点相互发送的有效载荷。确认和窗口大小都是字段TCPSegment 结构。
您可能需要注意length_in_sequence_space()方法,它计算一个段占据多少序列号(包括事实SYN 和 FIN 都按顺序计为一个位置,以及有效负载的每个字节)。
接下来说一下你的 TCPReceiver 将提供的接口:
     //! \brief The ackno that should be sent to the peer
    std::optional<WrappingInt32> ackno() const;

    //! \brief The window size that should be sent to the peer
    size_t window_size() const;
//! \brief number of bytes stored but not yet reassembled size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); } //! \brief handle an inbound segment void segment_received(const TCPSegment &seg);

 

TCPReceiver 是围绕您的 StreamReassembler 构建的。我们已经实施了.hh 文件中的构造函数和未组装的字节和流输出方法。

3.3.1 segment_received()

这是主要的方法!
该方法需要:
  • 如有必要,设置初始序列号。第一个的序列号 -设置了 SYN 标志的到达段是初始序列号。你会想要跟踪它,以便在 32 位封装的 seqnos/acknos 之间进行转换以及它们的绝对等价物。(请注意,SYN 标志只是标头中的一个标志。同一段也可以携带数据,甚至可以设置 FIN 标志。)
• 将任何数据或流结束标记推送到StreamReassembler。如果FIN 标志设置在 TCPSegment 的标头中,这意味着流以最后一个负载的字节——它相当于一个 EOF。
• 确定线段的任何部分是否落在窗口内。这种方法
如果段的任何部分落入窗口内,则应返回 true,否则返回 false-
否则。2
这就是我们的意思:一个段占据一个序列范围
数字——从它的序列号开始的范围,长度等于它的
序列 space() 中的长度(这反映了 SYN 和 FIN 每个计数的事实
一个序列号,以及有效载荷的每个字节)。一个段是可以接受的
(并且该方法应该返回true)如果它占据的任何序列号下降
在接收器的窗口内。
有一些特殊情况:
– 如果尚未设置 ISN,则当(且仅当)具有SYN 位设置。
– 如果窗口大小为零,那么它的大小应该被视为一个字节目的是确定窗口的第一个和最后一个字节。
– 如果序列空间中的段长度为零(例如,只是一个裸确认没有有效载荷,也没有 SYN 或 FIN 标志),那么它的大小应该被视为一个字节,用于确定它占用的第一个和最后一个序列号。

3.3.2 ackno()

返回一个可选的<WrappingInt32> 包含接收者还不知道第一个字节的序列号。这是窗口的左边缘:接收器有兴趣接受的的第一个字节。
1如果尚未设置 ISN,则返回一个空的可选。
2如果为 false,则 TCPSender 不知何故弄糊涂了,需要对窗口进行更正。
3这一可接受性定义是对 RFC 793,第 25 至 26 页中定义的轻微改编。

3.3.3 window_size()

有关窗口大小的定义,请参阅 §§ 3.2

实验结果:

 

实验总结:

    本实验是实现一个TCP接收端,接受TCPsegment,需要提前了解项目中tcp_helper包下的TCPSegment类

需要对syn,fin进行判断处理,提取data并发送到流重组器。

实现细节:

 

 

 1.序列号与绝对序列号转换

由于seg序列号只有32位,并且为了提高网络安全性,初始字节号(isn)是随机的,可能会越界,我们需要添加绝对序列号,

而绝对序列号是流序列号去除syn和fin,所以我实现的时候直接用的流序列号,把第一个字节(图中的'c'字节)作为isn,而不是syn。

 绝对序列号和序列号之间的转换在wrap和unwrap中实现,checkpoint为已经重组字节流的大小

2.窗口大小

窗口大小就是(总容量-字节流缓冲区容量)

3.syn和fin控制

如果还没收到syn,除非该包带有syn,否则就丢弃该包。

如果fin=true,数据读入流重组器时标记eof=true;

4.判断是否可接受

先得到窗口两端以及seg数据起始字节的绝对序列号,只要数据和窗口有交集,则将seg所带data读入流重组器

5.ackno

接收端发送的ackno的范围是syn_seqno+1到fin_seqno+fin_datasize+1。

字节流读取的字节长度就是已近重组的数据长度,也就是我们需要返回的下一个需要发送字节的绝对序列号,调用wrap把其转换为32位序列号返回即可。

需要注意的是,如果数据完全被重组,需要返回fin的确认信号,而不再是最后一个重组字节的确认信号。

代码:

wrapping_integers.cc

const uint64_t max_int=4294967296;
using namespace std;

WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
    WrappingInt32 res(static_cast<uint32_t>((static_cast<uint64_t>(isn.raw_value())+n)%max_int));
    return res;
}
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
    uint64_t mid = (max_int - static_cast<uint64_t>(isn.raw_value()) + static_cast<uint64_t>(n.raw_value())) % max_int;
    mid += (checkpoint / max_int) * max_int;
    uint64_t right = mid + max_int;
    uint64_t left = mid - max_int;
    if (mid > checkpoint) {
        if (mid - checkpoint < checkpoint - left||mid<max_int) {
            return mid;
        }
        else {
            return left;
        }
    }
    else {
        if (right - checkpoint < checkpoint - mid) {
            return right;
        }
        else {
            return mid;
        }
    }
}

 

tcp_receiver.hh

class TCPReceiver {
    //! Our data structure for re-assembling bytes.
    StreamReassembler _reassembler;

    //! The maximum number of bytes we'll store.
    size_t _capacity;
    bool recieve_isn=false;
    WrappingInt32 isn=WrappingInt32(0u);
    uint64_t fin_off_set=UINT64_MAX;
.........

 

tcp_receiver.cc

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

// Dummy implementation of a TCP receiver

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


const uint64_t max_int=4294967296;
using namespace std;
void TCPReceiver::segment_received(const TCPSegment &seg) {
    if(seg.header().syn){
        recieve_isn=true;
        isn=wrap(1ull,seg.header().seqno);
    }
    if(!recieve_isn){
        return ;
    }
  const uint64_t check_point=static_cast<uint64_t>(_reassembler.stream_out().bytes_written());
  const uint64_t win_left=check_point;
  const uint64_t win_right=static_cast<uint64_t>(_capacity-_reassembler.stream_out().buffer_size())+win_left-1;
  const uint64_t seg_start=seg.header().syn?0:unwrap(seg.header().seqno,isn,check_point);
    if(seg_start>win_right||seg_start+seg.length_in_sequence_space()<=win_left){
        return ;
    }
    string data=string(seg.payload().str().data(),seg.payload().size());
    if(seg.header().fin){
        fin_off_set=seg_start+data.size();
    }
    _reassembler.push_substring(data,seg_start,seg.header().fin);
}

optional<WrappingInt32> TCPReceiver::ackno() const { 
    if(recieve_isn){
       if(fin_off_set==_reassembler.stream_out().bytes_written()){
         return wrap(1ull+static_cast<uint64_t>(_reassembler.stream_out().bytes_written()),isn);
       }else{
           return wrap(static_cast<uint64_t>(_reassembler.stream_out().bytes_written()),isn);
       }
    }
    return {};
 }

size_t TCPReceiver::window_size() const {   
   return _capacity-_reassembler.stream_out().buffer_size();
 }

 

posted @ 2021-08-24 11:31  cono奇犽哒  阅读(261)  评论(0编辑  收藏  举报