wrap()
和 unwrap()
函数, 以完成序列号和相对序列号之间的转换.该转换相对简单, 直接在初始序列号 ISN 的基础上加上相对序列号即可, 由于序列号是 32 位, 相对序列号是 64 位, 因此相对序列号只需要考虑其低 32 位即可.
该转换是该部分实验相对难的地方, 关键在于有一个检查点 c h e c k p o i n t checkpoint checkpoint, 由于序列号位数比相对序列号少, 因此转换后的相对序列号应该满足是最接近检查点的.
libsponge/wrapping_integers.cc
#include "wrapping_integers.hh"
// Dummy implementation of a 32-bit wrapping integer
// 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;
//! 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 isn + static_cast<uint32_t>(n); }
//! 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) {
const uint32_t offset = n - isn - static_cast<uint32_t>(checkpoint);
uint64_t res;
if (offset <= (1U << 31)) {
res = checkpoint + offset;
} else {
// if `res` has 64 bit underflow, chose the right one.
res = checkpoint - ((1UL << 32) - offset);
if (res > checkpoint) {
res = checkpoint + offset;
}
}
return res;
}
unwrap()
对于 unwrap()
的实现, 有如更加简洁的代码:
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
int32_t offset = static_cast<uint32_t>(checkpoint) - (n - isn);
int64_t result = checkpoint - offset;
return result >= 0 ? result : result + (1UL << 32);
}
该实现的基本思想和之前的实现代码的思想是相同的.
int32_t
. 这样对于在检查点左右两侧的情况都可以用 c h e c k p o i n t + o f f s e t checkpoint+offset checkpoint+offset 表示.int32_t
表示的范围为 { c h e c k p o i n t − 2 31 ≤ x ≤ c h e c k p o i n t + 2 31 − 1 ∣ x ∈ Z } \{checkpoint-2^{31}\leq x\leq checkpoint+2^{31}-1|x\in Z\} {checkpoint−231≤x≤checkpoint+231−1∣x∈Z}, 与 B B B 在端点处正好相反, 因此这里将 o f f s e t offset offset 表示为 u i n t 32 _ t ( c h e c k p o i n t ) − d e l t a uint32\_t(checkpoint)-delta uint32_t(checkpoint)−delta. 这样在检查点左右两侧的情况都可以用 c h e c k p o i n t − o f f s e t checkpoint-offset checkpoint−offset 表示.int64_t
, 这样对于上述 64 溢出情况, r e s u l t result result 的值就会变为负数, 此时只需要再这基础上加 2 32 2^{32} 232 即可.TCPReceiver
的 segment_received()
、ackno()
和 window_size()
三个方法实现思路基本按照任务指导, 需要注意的是 TCPReceiver
接收到的是 TCP 报文段 TCPSegment
, 其中报文首部记录的均为序列号(seqno), 而 TCPReceiver
内部使用的 StreamReassembler
实际上使用的是流索引(stream index), 过程中需要借助 unwrap()
及 wrap()
函数进行转换. 而这其中就需要使用 ISN 进行转换, 因此需要添加一个 _isn
的私有成员记录该 TCP 连接的 ISN. 值得一提的是, 在未收到 SYN
标志位时, 没有 ISN, 因此最终使用 std::optional
作为 _isn
的类型.
对于 segment_received()
函数, 需要注意的有: 在接收到 SYN
报文段之前的报文都是无效报文, 需要丢弃不做处理. 在转换序列号到流索引时, 需要一个检查点(checkpoint), 根据指导书前文, 检查点是最后一个重组字节的相对序列号, 而 stream_out().bytes_written()
表示已经写入 ByteStream
字节流的字节数, 其值与最后一个重组的字节的相对序列号一致. 同时在使用 unwrap()
时需要注意 ISN 同样占一个序列号, 因此对于其负载的数据的序列号需要额外加 1.
对于 ackno()
函数, 在 ISN 未设置前需要返回空, 即 std::nullopt
, 反之返回下一个字节的序列号. stream_out().bytes_written()
表示的为最后重组字节的相对序列号, 加 1 即第一个未重组字节的相对序列号, 再通过 wrap()
即可转换为序列号. 同样需要注意, FIN
标志位也占用一个序列号, 因此在收到 FIN
之后, 序列号还要再加 1.
对于 window_size()
, 即第一个未重组字节和第一个不接受字节的间距, 也就是除去已重组的字节的空间大小. 由于 ByteStream
和 StreamRessembler
总容量为一致, 因此可以用 stream_out().remaining_capacity()
表示.
libsponge/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 sequence number of ISN
std::optional<WrappingInt32> _isn;
//! The maximum number of bytes we'll store.
size_t _capacity;
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), _isn(), _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
libsponge/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) {
const TCPHeader &header = seg.header();
if (!_isn.has_value()) {
// the segment before receiving SYN segment should be discarded
if (!header.syn) {
return;
}
// set the ISN
_isn = header.seqno;
}
// ISN occupies a seqno
// `stream_out.bytes_written()` is equal to the index(absolute seqno) of last reassembled byte, which is checkpoint
// use `unwrap()` to get absolute seqno, and minus 1 to get the stream index.
uint64_t stream_index = unwrap(header.seqno + header.syn, _isn.value(), stream_out().bytes_written()) - 1;
_reassembler.push_substring(seg.payload().copy(), stream_index, header.fin);
}
optional<WrappingInt32> TCPReceiver::ackno() const {
// if the ISN hasn’t been set yet, return an empty optional
if (!_isn.has_value()) {
return nullopt;
}
// `stream_out.bytes_written()+1` is the absolute seqno of the first unassembled byte
// FIN flag also occupies a seqno
return wrap(stream_out().bytes_written() + 1 + stream_out().input_ended(), _isn.value());
}
size_t TCPReceiver::window_size() const { return stream_out().remaining_capacity(); }