在lab0中,我们实现了流控制的字节流(ByteStream)。
在lab1中,我们创建了一个模块,该模块接受一系列子字符串,这些子字符串都是从相同的字节流中摘录的,并将它们重新组装回原始的字节流,同时将其内存消耗限制在给定的数量(容量)。
现在,在lab2中,我们将实现处理入站字节流的TCP部分:tcprecreceiver。当你编写StreamReassembler和ByteStream时,你已经完成了其中涉及的大部分算法工作;本周主要是关于将这些类连接到TCP格式。这将涉及到TCP如何表示每个字节在流中的位置——称为“序列号”,tcprecreceiver负责告诉发送方(a)它已经成功组装了多少入站字节流(这被称为“确认”)和(b)发送方现在允许发送的字节范围(“流量控制窗口”)。
下周,在lab3中,我们将实现处理出站字节流的TCP部分:TCPSender。最后,在lab4中,我们将结合前面的工作来创建一个工作的TCP实现:一个包含TCPSender和tcprecreceiver的TCPConnection。
在sponge目录下运行
git fetch
git merge origin/lab1-startercode
得到lab1的源码
在build目录下make,出错:
参考,需要安装libpcap-dev
sudo apt-get install libpcap-dev
再make就编译成功了
本周,我们将实现TCP的”receiver”部分,负责接收TCP段(实际的数据报有效负载),重新组装字节流(包括它的结束,当它发生时),并确定应该发回给发送者的信号,以进行确认和流控制。
确认的意思是,接收方需要的下一个字节的索引是什么,以便重新组装更多的字节流?这告诉发送方它需要发送或重发哪些字节。
流量控制意味着,接收方感兴趣和愿意接收什么范围的索引?”(通常作为剩余容量的函数)。这告诉发送者它允许发送多少
当SYN随机为2^32-2时以下三者的数值以及区别:
可以看到,absolute seqno和stream index之间相互转换很容易,只需要+1或者-1就好了。但是absolute seqno与 seqno之间相互转换比较麻烦,实验准备了这两者相互转换的数据类型和函数( wrapping integers.hh),具体实现需要我们自己动手(wrapping integers.cc)
分析:
(1)从 seqno转成absolute seqno
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn);
absolute seqno从ISN开始,则seqno=isn+absolute seqno
(2)从absolute seqno转成 seqno
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint);
参数:
n:seqno
isn:ISN
checkpoint:最近的absolute seqno
根据上面的seqno=isn+absolute seqno,我们可以推出absolute seqno=seqno-isn。但是absolute seqno与isn都是无符号32位,seqno是无符号64位,转换结果不唯一。比如seqno=4,isn=2,absolute seqno可能为4-2=2或者4+232-2=232+2,或者233+2……我们想要的是与check point最接近的转换结果,如果check point=1,那么我们想要的结果就是absolute seqno=2。
可以用上面实现的函数将checkpoint转化为WrappingInt32 checkpoint_seqno,差值seqno-checkpoint_seqno加在checkpoint上
比如上面例子里的check point=1,则checkpoint_seqno=isn+check point=2+1=3,差值=seqno-checkpoint_seqno=4-3=1,再将差值加在checkpoint上,得到absolute seqno=差值+checkpoint=1+1=2。
注意差值+checkpoint可能是负数,所以还要absolute seqno=absolute seqno>0?absolute seqno:absolute seqno+(1UL<<32)
//! 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
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
return WrappingInt32(static_cast<uint32_t>(n) + isn.raw_value());
}
//! 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.
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
// STEP ranges from -UINT32_MAX/2 to UINT32_MAX/2
// in most cases, just adding STEP to CHECKPOINT will get the absolute seq
// but if after adding, the absolute seq is negative, it should add another (1UL << 32)
// (this means the checkpoint is near 0 so the new seq should always go bigger
// eg. test/unwrap.cc line 25)
int32_t steps = n - wrap(checkpoint, isn);
int64_t res = checkpoint + steps;
return res >= 0 ? res : res + (1UL << 32);
}
流量控制窗口:目前接收方可以接收的字节索引范围
窗口的左边缘称为ackno,它是TCPReceiver不知道的第一个字节的索引(因此希望找到)。
窗口的右边缘是TCPReceiver不愿意接受的第一个索引
在本实验的剩余部分中,您将实现tcprecreceiver。它将(1)从它的对等端接收片段,(2)使用你的StreamReassembler重新组装字节流,并计算(3)确认号(ackno)和(4)窗口大小。
这是最主要的方法!每次从对等端收到一个新的段时,tcprecreceiver::segment received()将被调用。
返回窗口的左边缘:接收者想要接收的第一个字节。如果尚未设置ISN,则返回一个空的可选值
容量-TCPReceiver在字节流中持有的字节数
代码参考自千裡
stream_reassembler.hh
uint64_t getter_head_index() const { return _first_unassembled; }
tcp_receiver.hh
#ifndef SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#define SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#include "byte_stream.hh"
#include "stream_reassembler.hh"
#include "tcp_segment.hh"
#include "wrapping_integers.hh"
#include
//! \brief The "receiver" part of a TCP implementation.
//! 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;
//0:listen 1:syn_recv 2:fin_recv
uint64_t _state = 0;
uint32_t _isn = 0;
bool _syn = false;
bool _fin = false;
public:
//! \brief Construct a TCP receiver
//!
//! \param capacity the maximum number of bytes that the receiver will
//! store in its buffers at any give time.
TCPReceiver(const size_t capacity) : _reassembler(capacity), _capacity(capacity) {}
//! \name Accessors to provide feedback to the remote TCPSender
//!@{
//! \brief 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;
//!@}
//! \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);
//! \name "Output" interface for the reader
//!@{
ByteStream &stream_out() { return _reassembler.stream_out(); }
const ByteStream &stream_out() const { return _reassembler.stream_out(); }
//!@}
};
#endif // SPONGE_LIBSPONGE_TCP_RECEIVER_HH
tcp_receiver.cc
#include "tcp_receiver.hh"
// 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`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
void TCPReceiver::segment_received(const TCPSegment &seg) {
if (_state == 2) return;
//payload中实际上并不包含syn和fin
//size_t length_space; //seg 序列长度
//绝对序列号,同时作为下一次seg的checkpoint,用于将相对序列号转换成绝对序列号
//使用abs_seq确定checkpoint实际上,窗口不能太大否则checkpoint不好确定了
static size_t abs_seqno = 0;
size_t length = seg.length_in_sequence_space();
if (seg.header().syn == true) {
if (_syn == true) //重复建立链接,丢弃
return;
_syn = true;
_isn = seg.header().seqno.raw_value();
_state = 1;
abs_seqno = 1; //记录checkpoint,seg中第一个字节数据的绝对序号
length--;
//length_space = seg.length_in_sequence_space() - 1; //序列长度,不包括syn
if (seg.length_in_sequence_space() - 1 == 0) //只有syn
return;
} else if (!_syn) { //尚未建立连接就开始传送数据,丢弃
return;
} else {
//拆包成绝对地址(64位 初始位是0),同时获得该seg的index
abs_seqno = unwrap(WrappingInt32(seg.header().seqno.raw_value()), WrappingInt32(_isn), abs_seqno);
//length_space = seg.length_in_sequence_space();
}
if (seg.header().fin) length--;
// //处理超出窗口的和ackno之前的
if (abs_seqno >= _reassembler.getter_head_index() + 1 + window_size())
return;
if (seg.header().fin) _fin = true;
// if (abs_seqno + length <= _reassembler.getter_head_index() + 1)
// return;
//seq syn fin都已经处理。 最后处理payload
//abs_seqno -> index
_reassembler.push_substring(seg.payload().copy(), abs_seqno - 1, _fin);
if (_reassembler.stream_out().input_ended())
_state = 2;
return;
}
optional<WrappingInt32> TCPReceiver::ackno() const {
if (_state == 1) {
//把64位绝对位置+ISN打包成32位相对位置
//index ->-> abs_seq ->-> seq
// +1 wrap
return wrap( _reassembler.getter_head_index() + 1, WrappingInt32(_isn));
} else if (_state == 2){
return wrap( _reassembler.getter_head_index() + 2, WrappingInt32(_isn));
} else {
return nullopt;
}
}
size_t TCPReceiver::window_size() const {
//容量(buffer的最大长度) - 已经重组排好序的部分(buffer_size),把ressemble理解成对bytetream的一层封装
return { _capacity - _reassembler.stream_out().buffer_size()};
}