Overview
TCPReceiver 從對等的sender接收消息,使用 receive() 方法,然后調用 Reassembler() 方法,后者寫入 ByteStream 中 然后應用程序從 ByteSteam 中讀取。
同時,TCPReceiver 還會通過 send() 方法給sender發送消息,告訴sender:
- 確認號ackno ,這是接收方需要從發送方接受的第一個字節;
- ByteStream的可用容量,也就是窗口大小 window size。
本實驗最難的部分在于如何思考 TCP 將如何表示每個字節在流中的位置,也稱為“序列號”。
Translating between 64-bit indexes and 32-bit seqnos
TCP協議中包含三種序號分別是:seqno、absolute seqno、stream index,其轉換關系如圖所:示:
可以看到,absolute Seqno和stream index只是在開頭和結尾的SYN和FIN有差異,且都是64位,所以第一個問題是如何實現seqno和absolute seqno之間的轉換。
absolute seqno 到 seqno
首先,seqno有一個隨機數ISN,作為起始序列編碼,而absolute seqno從0開始,可以得到兩者之間的轉換關系為:
s e q n o ≡ a b s o l u t e s e q n o + I S N ( m o d 2 32 ) seqno \equiv absolute \, seqno + ISN (mod \, 2^{32}) seqno≡absoluteseqno+ISN(mod232)
所以wrap函數實現如下:
Wrap32 Wrap32::wrap( uint64_t n, Wrap32 zero_point )
{// Your code here.return Wrap32 { zero_point.raw_value_ + static_cast<uint32_t>(n)};
}
seqno 到 absolute seqno
和上面的類似,現在反向推倒absolute seqno
a b s o l u t e s e q n o ≡ s e q n o ? I S N ( m o d 2 32 ) absolute \, seqno \equiv seqno - ISN (mod \, 2^{32}) absoluteseqno≡seqno?ISN(mod232)
這樣就可以得到absolute seqno的低32位,然后在函數中給出了checkpoint,現在的目標是找出離checkpoint最近的absolute seqno的高32位,這個值一定滿足:
c h e c k p o i n t ? 2 31 ≤ a b s o l u t e s e q n o ≤ c h e c k p o i n t + 2 31 checkpoint - 2^{31}\leq absolute \, seqno \leq checkpoint + 2^{31} checkpoint?231≤absoluteseqno≤checkpoint+231
之所以滿足這個條件,是因為如果不在這個范圍內,則可以通過+或者- 2 32 2^{32} 232來更接近checkpoint。
所以高32位的計算方法是通過給checkpoint + 或者 - 2 31 2^{31} 231再取出其中的高32位,然后找出兩個中接近checkpoint的值,即為所求。
uint64_t Wrap32::unwrap( Wrap32 zero_point, uint64_t checkpoint ) const
{// Your code here.uint32_t low32_bits = this->raw_value_ - zero_point.raw_value_; uint64_t high32_bits1 = (checkpoint + (1 << 31)) & 0xFFFFFFFF00000000;uint64_t high32_bits2 = (checkpoint - (1 << 31)) & 0xFFFFFFFF00000000;uint64_t aseqno1 = low32_bits | high32_bits1;uint64_t aseqno2 = low32_bits | high32_bits2;if(max(aseqno1,checkpoint) - min(aseqno1,checkpoint) < max(aseqno2,checkpoint) - min(aseqno2,checkpoint))return aseqno1;return aseqno2;
}
Implementing the TCP receiver
實驗的剩余部分,要求我們實現TCPReceiver,實現:
- 從sender接收消息,并調用Reassembler對ByteStream重組;
- 將包含確認號ackno和窗口大小發送給sender.
首先添加幾個成員變量和修改構造函數
class TCPReceiver
{
public:// Construct with given Reassemblerexplicit TCPReceiver( Reassembler&& reassembler ) : reassembler_( std::move( reassembler ) ) ,isn(-1), open(false),_capacity (std::min((size_t)reassembler_.writer().available_capacity(),(size_t)UINT16_MAX)){}/** The TCPReceiver receives TCPSenderMessages, inserting their payload into the Reassembler* at the correct stream index.*/void receive( TCPSenderMessage message );// The TCPReceiver sends TCPReceiverMessages to the peer's TCPSender.TCPReceiverMessage send() const;// Access the output (only Reader is accessible non-const)const Reassembler& reassembler() const { return reassembler_; }Reader& reader() { return reassembler_.reader(); }const Reader& reader() const { return reassembler_.reader(); }const Writer& writer() const { return reassembler_.writer(); }private:Reassembler reassembler_;Wrap32 isn; // zero_pointbool open; // ISNsize_t _capacity; // 容量,最大為UINT16_MAX
};
然后修改成員函數,其中有三個特殊的標志位,分別是:
SYN:接收到SYN才開始傳輸報文,此前的全部丟棄
FIN:接收到FIN就結束傳輸報文
RST:接收到RST就將ByteSteam置錯,并且停止傳輸。
所以在receive函數里:
- 先判斷RST信號,如果出現RST,就給reader()置錯;
- 判斷是否有SYN,之前沒有就丟棄,否則就記錄ISN;
- 根據已經push進重組器的字節數為checkpoint,以ISN和checkpoint調用前面寫的unwarp函數計算絕對序列號;
- 如上一問所述,在stream中索引并不是完全和64位的絕對索引相同的,stream中的索引不包括SYN和FIN,所以要判斷一下。
值得注意的是,我第一次在實現這個轉換的時候以為SYN始終都不會進入stream中,實際上不是這樣的,第一個SYN的序列號作為ISN是會進入stream中,而其后的各報文的SYN端不會進入stream,FIN在后文也需要判斷,所以我們需要根據報文的SYN標志對stream index和absolute seqno之間進行轉換。
接下來在send函數,其中RST根據reader()是否出現error,而windows size則根據總大小減去重組器中已經存儲的大小,接下來主要介紹ackno的計算:
- 如果還沒有建立連接,即還沒有SYN,就直接回復空(nullopt);
- 否則,就根據已經push進重組器的字節 + 1(確認號是下一個需要的序列號,所以加1),這里又需要判斷是否是FIN,如果是,因為FIN也要占一個絕對序列號,所以還要再加1;
- 調用wrap轉換成seqno,并返回message。
void TCPReceiver::receive( TCPSenderMessage message )
{// Your code here.if ( message.RST ) {reassembler_.reader().set_error();return;} if ( open == false ) {if ( !message.SYN )return;else {open = true;isn = message.seqno;}}Wrap32 seqno = message.seqno;uint64_t checkpoint = reassembler_.writer().bytes_pushed();uint64_t ab_seqno = seqno.unwrap( isn, checkpoint );uint64_t index = message.SYN ? ab_seqno : ab_seqno - 1;reassembler_.insert( index, message.payload, message.FIN );
}TCPReceiverMessage TCPReceiver::send() const
{// Your code here.TCPReceiverMessage message;if ( !open ) {message.ackno = nullopt;} else {uint64_t ab_seqno = reassembler_.writer().bytes_pushed() + 1 + ( reassembler_.writer().is_closed() ? 1 : 0 );message.ackno = Wrap32::wrap( ab_seqno, isn );}message.RST = reassembler_.reader().has_error();message.window_size = _capacity - reassembler_.reader().bytes_buffered();return message;
}
實驗結果