1. 實驗目的
lab2 的目的是實現tcp
的接收端。
主要包括兩方面
(1) 從發送端接收消息,使用Reassembler
聚合字節流(Bytestream
)
(2)將確認號(ackno
)和window size
發回對端
確認號,也就是first_unassembler byte
; 而Bytestream
可寫入的大小,
也就是window size
!
ackno
和window size
兩個共同描述了發送方能發送的數據范圍。有時我們也叫
ackno?left?edgeackno+window?size?right?edge ackno \Leftrightarrow left \ edge\\ ackno + window\ size \Leftrightarrow right\ edge ackno?left?edgeackno+window?size?right?edge
這個實驗中最困難的部分在于TCP如何在流中表示每一個字節,也就是序列號(sequence number
)。
2 實驗內容
2.1 序列空間的轉換
個人感覺是這個實驗比較難的一部分。
我們在Reassebler
中的索引是64位的,它足夠大幾乎不會重疊。
(假如傳輸速度為100Gbps
, 幾乎要花50年才能到達2642^{64}264, 而只需要1/3秒就能到達2322^{32}232)。
但在TCP中的序列空間是寶貴的,只有32位。這就帶來了一些額外的問題:
wrap arround
回滾,32位的序列空間只有4GB,0~232?12^{32}-1232?1, 超過了這個范圍就又會從0開始TCP seq number
TCP序列號為了安全,是從一個隨機數開始的,它并不從0開始。開始的數我們叫它ISN(Initial Sequance Number
, 初始隨機數)。SYN FIN
各自占據一個序列號空間
我們這里就涉及到三種序列號索引空間了。
- TCP 序列號索引
- 絕對序列號
- 流索引
文檔中也給了差別比較的表格
序列號索引就是在TCP中的數字;而絕對序列號就是以0開始的,它不會發生回滾。面流索引就是收到的數據的標號了,就不包括SYN FIN
。
流索引和絕對序列索引就只差一個1
。
而序列號與絕對序列號的轉換就需要我們實現了。
從絕對序列號轉換為序列號,比較簡單。
只需要zero_point + n
就可以了。返回值就會自動回滾。
而讓我感覺到最困難的就是323232位的序列號轉換成646464位的絕對序列號了。
事實上文檔中給出了解決的方法了,它的提示說不管如何轉換。在32位的
序列號中的差值和64位絕對序列號中的差值肯定是一樣的。
而它函數中的checkpoint
則是離當前64位序列號最近的一個, 用first unassembler idx
來充當。
為什么需要這個checkpoint
呢?因為一個32位的seq number可以對應很多個64位的絕對序列號!!!
比如seqno=7
,就可能對應7 7+2^{32} 7 + 2^{33} ...
, 因此需要一個checkpoint
來確定到底是哪一個,我們要的是離checkpoint
最近的,比如說seqno = 7, checkpoint =6 + 2^{32}
,我們一下就能確定abs seqno=7 +2^{32}
。
這里還有個難點就是理解這個最近,由于32位置的空間它是回滾的,因此
距離也是有兩個的!!!
還是看代碼吧!
*wrapping_integers.cc
#include "wrapping_integers.hh"
#include "debug.hh"using namespace std;Wrap32 Wrap32::wrap( uint64_t n, Wrap32 zero_point )
{// Your code here.// debug( "unimplemented wrap( {}, {} ) called", n, zero_point.raw_value_ );return zero_point + static_cast<uint32_t>( n & 0xFFFFFFFF );
}uint64_t Wrap32::unwrap( Wrap32 zero_point, uint64_t checkpoint ) const
{// Your code here.// debug( "unimplemented unwrap( {}, {} ) called", zero_point.raw_value_, checkpoint );auto check_seq = wrap( checkpoint, zero_point).raw_value_;static constexpr uint64_t _2pow32 = static_cast<uint64_t> (1) << 32;uint64_t df1 = static_cast<uint64_t>( raw_value_ ) + _2pow32 - check_seq;if ( df1 >= _2pow32)df1 -= _2pow32;auto df2 = _2pow32 - df1;if ( df2 < df1 && checkpoint >= df2)return checkpoint - df2;elsereturn checkpoint + df1;}
2.2 實現tcp_receiver
實現這個tcp_receiver
倒是沒有實現序列號花的時間多。
狀態管理也沒有用狀態機的那一套,用if-else
過完樣例。。。
主要遇到問題是,由于SYN
它是需要占據一個序列號, 也就是ISN
,而
之后數據中的zero_point
應該是ISN + 1
了。還有些小問題后面遇到測試
樣例排查一下也能過。
最終還是看代碼吧!
#include "tcp_receiver.hh"
#include "debug.hh"using namespace std;void TCPReceiver::receive( TCPSenderMessage message )
{// Your code here.// debug( "unimplemented receive() called" );// (void)message;if ( message.RST ) {this->reassembler_.reader().set_error();return ;}if ( message.SYN && (not is_con)) {ISN = message.seqno ;is_con = true;}if ( is_con ) {Wrap32 cur{message.seqno};if (message.SYN)cur = cur + 1;uint64_t first_idx_ = cur.unwrap( ISN.value() + 1 , this->reassembler_.get_first_unassembler_idx());debug("payload: {}, size: {}, FIN: {}", message.payload, message.payload.size(), message.FIN ? "true": "false");this->reassembler_.insert( first_idx_, message.payload, message.FIN);}// first_idx
}TCPReceiverMessage TCPReceiver::send() const
{// Your code here.// debug( "unimplemented send() called" );// return {};TCPReceiverMessage msg{};if ( is_con ) {auto ackno = reassembler_.get_first_unassembler_idx();if (reassembler_.writer().is_closed())ackno++;msg.ackno = Wrap32::wrap( ackno, ISN.value() + 1);}auto wnd_sz = reassembler_.writer().available_capacity() ;msg.window_size = wnd_sz > UINT16_MAX ? UINT16_MAX : wnd_sz ;msg.RST = reassembler_.writer().has_error();return msg;
}
3. 遇到的問題
- 序列號轉絕對序列號
FIN SYN
需要占據一個序列號空間,這影響到了unwrap
中的zero_point
,同時在最后關閉時,ackno
也需要多加1bytestream
中的available_capacity
是uint64_t
, 而序列號空間最大UINT16_MAX
,需要進行限制RST
標志位,需要根據RST
來設置bytestream.set_error()