Stanford CS144 Lab2.TCP Reciever
于2022年4月18日2022年4月18日由Sukuna发布
CS144 Lab2 TCP 接收端的实现
绝对序号和相对序号的转换:
在实践中,一个分组的序号承载在分组首部的一个固定长度的字段中。如果分组序号字段的比特数是k,则该序号范围是[0,2^k]。 在一个有限的序号范围内,所有涉及序号的运算必须使用模2^k运算。(即序号空间可被看作是一个长度为2^k 的环,其中序号2^k-1紧挨着0)。上面论述的序号是相对序号(相对序号的开始值是isn),还有一种不模2^k的运算就是绝对序号.
这个时候我们需要完成两个函数:
1.wrap(绝对序号转化为相对序号)
代码语言:javascript复制WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
DUMMY_CODE(n, isn);
WrappingInt32 res(n isn.raw_value());
return res;
}
这个函数调用了WrappingInt32
类的构造函数,构造函数获得一个int类型的数(uint_64等类型)然后取模之后获得32位的整形数,存放到raw_value成员中.
2.unwrap(相对序号转绝对序号)
代码语言:javascript复制uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
DUMMY_CODE(n, isn, checkpoint);
uint64_t temp=n.raw_value()-isn.raw_value();
if(checkpoint==0){
return temp;
}
uint32_t div=checkpoint/(1ul<<32);
uint32_t res=checkpoint%(1ul<<32);
if (res<=temp) {
temp=(checkpoint-temp-(div-1)*(1ul<<32))<(temp div*(1ul<<32)-checkpoint)?temp (div-1)*(1ul<<32):temp div*(1ul<<32);
}else{
temp=(checkpoint-temp-div*(1ul<<32))<(temp (div 1)*(1ul<<32)-checkpoint)?temp div*(1ul<<32):temp (div 1)*(1ul<<32);
}
return temp;
}
给定checkpoint,找到最靠近checkpoint的那个temp,返回即可.
Implementing the TCP receiver
首先我们看一看TCP报文包的定义:主要是由首部和其中的元素组成:其中可以调用serialize和parse方法转化,
代码语言:javascript复制class TCPSegment {
private:
TCPHeader _header{};
Buffer _payload{};
public:
//! brief Parse the segment from a string
ParseResult parse(const Buffer buffer, const uint32_t datagram_layer_checksum = 0);
//! brief Serialize the segment to a string
BufferList serialize(const uint32_t datagram_layer_checksum = 0) const;
//! name Accessors
//!@{
const TCPHeader &header() const { return _header; }
TCPHeader &header() { return _header; }
const Buffer &payload() const { return _payload; }
Buffer &payload() { return _payload; }
//!@}
//! brief Segment's length in sequence space
//! note Equal to payload length plus one byte if SYN is set, plus one byte if FIN is set
size_t length_in_sequence_space() const;
};
接着我们来看一看TCP首部:首部的元素主要是:
- 序号:seqno,占32位,用来标识从发送端到接收端的字节流;
- 确认号:ackno,占32位,只有ACK标志位为1时,确认号才有效,ackno=seqno 1;
- 标志位:
- SYN:发起一个连接;
- FIN:释放一个连接;
- ACK:确认序号有效。
struct TCPHeader {
static constexpr size_t LENGTH = 20; //!< [TCP](ref rfc::rfc793) header length, not including options
//! struct TCPHeader
//! ~~~{.txt}
//! 0 1 2 3
//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! | Source Port | Destination Port |
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! | Sequence Number |
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! | Acknowledgment Number |
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! | Data | |U|A|P|R|S|F| |
//! | Offset| Reserved |R|C|S|S|Y|I| Window |
//! | | |G|K|H|T|N|N| |
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! | Checksum | Urgent Pointer |
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! | Options | Padding |
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! | data |
//! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//! ~~~
//! name TCP Header fields
//!@{
uint16_t sport = 0; //!< source port
uint16_t dport = 0; //!< destination port
WrappingInt32 seqno{0}; //!< sequence number
WrappingInt32 ackno{0}; //!< ack number
uint8_t doff = LENGTH / 4; //!< data offset
bool urg = false; //!< urgent flag
bool ack = false; //!< ack flag
bool psh = false; //!< push flag
bool rst = false; //!< rst flag
bool syn = false; //!< syn flag
bool fin = false; //!< fin flag
uint16_t win = 0; //!< window size
uint16_t cksum = 0; //!< checksum
uint16_t uptr = 0; //!< urgent pointer
}
接着看一看TCP receiver的数据结构定义:
代码语言:javascript复制#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 <optional>
//! 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.
//接收重组segments为 ByteStream,并计算确认号和窗口大小以通告回远程 TCPSender。
class TCPReceiver {
//! Our data structure for re-assembling bytes.
//我们用于重新组装字节的数据结构。
StreamReassembler _reassembler;
//! The maximum number of bytes we'll store.
//容量大小
size_t _capacity;
WrappingInt32 ISN;
bool syn_flag;
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.
//构造函数,构造一个 TCP 接收器,容量接收器在任何给定时间将存储在其缓冲区中的最大字节数。
TCPReceiver(const size_t capacity) : _reassembler(capacity), _capacity(capacity),ISN(0) ,syn_flag(0){}
//! 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.
// 如果没有收到 SYN,则应发送给对等方的 ackno 为空
//这是接收器窗口的开始,否则,接收器未接收到的流中第一个字节的序列号。
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(); }
bool recv_fin() const;
//!@}
};
我们知道TCP需要接受一个叫做segment类型的数据,然后存储起来,送入到Lab1已经实现好的reassemble_stream中.并返回适合的ACK.
对于接受的数据:分成两种可能,一种是第一个序列,另外的就是普通的数据
代码语言:javascript复制void TCPReceiver::segment_received(const TCPSegment &seg) {
DUMMY_CODE(seg);
//代表第一个传过来的seg
if(seg.header().syn){
syn_flag= true;
//窗口的左端
ISN=seg.header().seqno;
} else if(!syn_flag){
return;
}
//推断数据包的序号,序号比较靠近上一个已经接收到的序号,然后塞进我们在Lab1已经写好的流重组器.
uint64_t received_lens=_reassembler.stream_out().bytes_written();
size_t index= unwrap(seg.header().seqno,ISN,received_lens);
if(!seg.header().syn){
index--;
}
//进行重组
_reassembler.push_substring(seg.payload().copy(),index,seg.header().fin);
}
ACK的返回也很简单,流重组器输入到Byte stream的个数就代表已经输入了多少个有序的序列,返回对应的ACK即可.但是对于结束的时候的ACK回应,我们还是需要分类讨论.
代码语言:javascript复制optional<WrappingInt32> TCPReceiver::ackno() const {
if(!syn_flag){
return std::nullopt;
}else{
//判断是否是最后一个
if(_reassembler.stream_out().input_ended()){
return ISN _reassembler.stream_out().bytes_written() 2;
}else{
//返回的ACK的序号就是期望获得的下一个字符的数 1,流重组器的已连续写入的数据量就是最后一个有序的 //字符
return ISN _reassembler.stream_out().bytes_written() 1;
}
}
}