持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
在 Lab2,我们将实现一个 TCPReceiver,用以接收传入的 TCP segment 并将其转换成用户可读的数据流。
TCPReceiver 除了将读入的数据写入至 ByteStream 中以外,它还需要告诉发送者两个属性:
- 第一个未组装的字节索引,称为确认号ackno,它是接收者需要的第一个字节的索引。
- 第一个未组装的字节索引和第一个不可接受的字节索引之间的距离,称为 窗口长度window size。
ackno 和 window size 共同描述了接收者当前的接收窗口。接收窗口是 发送者允许发送数据的一个范围,通常 TCP 接收方使用接收窗口来进行流量控制,限制发送方发送数据。
总的来说,我们将要实现的 TCPReceiver 需要做以下几件事情:
- 接收TCP segment
- 重新组装字节流(包括EOF)
- 确定应该发回给发送者的信号,以进行数据确认和流量控制
要求
需要实现一些类成员函数
-
segment_received(): 该函数将会在每次获取到 TCP 报文时被调用。该函数需要完成:-
如果接收到了 SYN 包,则设置 ISN 编号。
注意:SYN 和 FIN 包仍然可以携带用户数据并一同传输。同时,同一个数据包下既可以设置 SYN 标志也可以设置 FIN 标志。
-
将获取到的数据传入流重组器,并在接收到 FIN 包时终止数据传输。
-
-
ackno():返回接收方尚未获取到的第一个字节的字节索引。如果 ISN 暂未被设置,则返回空。 -
window_size():返回接收窗口的大小,即第一个未组装的字节索引和第一个不可接受的字节索引之间的长度。
TCPReceiver
class TCPReceiver {
WrappingInt32 _isn; //初始syn num
bool _set_syn_flag; //是否收到syn报文
StreamReassembler _reassembler; //重组器
size_t _capacity;//容量
public:
TCPReceiver(const size_t capacity) : _isn(0), _set_syn_flag(false), _reassembler(capacity), _capacity(capacity) {}
std::optional<WrappingInt32> ackno() const; //接收端需要的ackno
size_t window_size() const; //接收端窗口大小
size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); } //没重组好的字节数
void segment_received(const TCPSegment &seg); //将收到的TCPSegment写入字节流中
ByteStream &stream_out() { return _reassembler.stream_out(); }
const ByteStream &stream_out() const { return _reassembler.stream_out(); }//字节流
};
#include "tcp_receiver.hh"
#include <cassert>
// 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;
/**
* \brief 当前 TCPReceiver 大体上有三种状态, 分别是
* 1. LISTEN,此时 SYN 包尚未抵达。可以通过 _set_syn_flag 标志位来判断是否在当前状态
* 2. SYN_RECV, 此时 SYN 抵达。只能判断当前不在 1、3状态时才能确定在当前状态
* 3. FIN_RECV, 此时 FIN 抵达。可以通过 ByteStream end_input 来判断是否在当前状态
*/
void TCPReceiver::segment_received(const TCPSegment &seg) {
// 判断是否是 SYN 包
const TCPHeader &header = seg.header();
if (!_set_syn_flag) {
// 注意 SYN 包之前的数据包必须全部丢弃
if (!header.syn)
return;
_isn = header.seqno;
_set_syn_flag = true;
}
uint64_t abs_ackno = _reassembler.stream_out().bytes_written() + 1;
uint64_t curr_abs_seqno = unwrap(header.seqno, _isn, abs_ackno);
//! NOTE: SYN 包中的 payload 不能被丢弃
//! NOTE: reassember 足够鲁棒以至于无需进行任何 seqno 过滤操作
uint64_t stream_index = curr_abs_seqno - 1 + (header.syn);
_reassembler.push_substring(seg.payload().copy(), stream_index, header.fin);
}
optional<WrappingInt32> TCPReceiver::ackno() const {
// 判断是否是在 LISTEN 状态
if (!_set_syn_flag)
return nullopt;
// 如果不在 LISTEN 状态,则 ackno 还需要加上一个 SYN 标志的长度
uint64_t abs_ack_no = _reassembler.stream_out().bytes_written() + 1;
// 如果当前处于 FIN_RECV 状态,则还需要加上 FIN 标志长度
if (_reassembler.stream_out().input_ended())
++abs_ack_no;
return WrappingInt32(_isn) + abs_ack_no;
}
size_t TCPReceiver::window_size() const { return _capacity - _reassembler.stream_out().buffer_size(); }