TCP Receiver Index和Seqno的转换为了节省在TCP Header
当中的空间,在StreamReassembler
里面写的index
虽然是一个uint64_t
的类型,但是在实际的Header
中是使用一个uint32_t
的seqno
来进行标记位置的。对于uint32_t
的seqno
和uint64_t
的index
的相互转换则是通过以4GiB (2^32 bytes)
为一个长度进行取模来实现。
同时为了提高TCP
本身的安全性,并且确保每次获得的segments
数据段都是来自于本次连接的,因此提出了ISN(Initial Sequence Number)
的概念,即本次链接是从序号为isn
开始作为seqno
进行通信,大于isn
的seqno
所代表的index
是本次链接所需要的数据段,早于isn
的seqno
则是来自于上一次连接的老数据段,并不需要处理。
如果想要将uint32_t
的seqno
转为一个uint64_t
则需要一个checkpoint
作为定位,防止seqno
被定位到错误的位置上。这个checkpoint
在实现中就是最后一个重新组装后的字符位置
按lab2的原文:In your TCP implementation, you’ll use the index of the last reassembled byte as the checkpoint.
通过寻找距离checkpoint
最近的seqno
就可以定位到本来需要插入的seqno
位置了
代码思路对于将uint32_t
转为uint64_t
的代码实现很简单,只需要将uint64_t
的index
加上isn
的值之后对2^32
进行取模就行了,具体代码实现如下
1 2 3 4 WrappingInt32 wrap (uint64_t n, WrappingInt32 isn) { uint64_t result = (n + isn.raw_value ()) % (static_cast <uint64_t >(UINT32_MAX) + 1 ); return WrappingInt32 (static_cast <uint32_t >(result)); }
而对于将wrap
后的seqno
转回index
,我直接通过类似分类讨论的枚举找到了四个临界点,只需要判断checkpoint
相对于临界点的位置就可以得到答案。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 uint64_t unwrap (WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) { const uint64_t L = (1ul << 32 ); const uint64_t a = (checkpoint / L) * L - isn.raw_value () + n.raw_value (); if (checkpoint > a + (L * 3 ) / 2 ) { return a + 2 * L; } else if (checkpoint > a + L / 2 ) { return a + L; } else if (checkpoint < L) { return n.raw_value () < isn.raw_value () ? a + L : a; } else { return checkpoint < a - L / 2 ? a - L : a; } }
详细思路点这里(硬分类,感觉好蠢,但是有效.jpg)通解推导 checkpoint > L由以下公式
( i n d e x + i s n ) m o d L = s e q n o \left ( index+isn \right )\mod{ L } = seqno ( i n d e x + i s n ) m o d L = s e q n o
通过推导可以得到
i n d e x = s e q n o + k ∗ L − i s n index = seqno + k * L - isn i n d e x = s e q n o + k ∗ L − i s n
因此如果需要得到离checkpoint
最近的index
就只需要找到合适的k
即可,在这里不妨设
m = c h e c k p o i n t / L m = checkpoint / L m = c h e c k p o i n t / L
取m
作为一个附近值,通过画图可以知道,在一般情况下,答案一定在checkpoint
附近的三个区间内
seqno - isn > 0在这种情况下,checkpoint
的前中后三个区间都存在,只要列举并讨论范围就很简单了
当seqno - isn
为正数的时候,index
可能的一个取值会落在第②个区间上,有
i n d e x ′ ′ = m ∗ L + s e q n o − i s n index'' = m * L + seqno - isn i n d e x ′ ′ = m ∗ L + s e q n o − i s n
此时第①区间和第③区间上的index
可以分别表示为
i n d e x ′ = i n d e x ′ ′ − L i n d e x ′ ′ ′ = i n d e x ′ ′ + L index' = index'' - L \\\\ index''' = index'' + L i n d e x ′ = i n d e x ′ ′ − L i n d e x ′ ′ ′ = i n d e x ′ ′ + L
对index'
、index''
和index'''
的中间值进行判断,很容易得到以下规律
a = m ∗ L + s e q n o − i s n L = U I N T 32 _ M A X + 1 { c h e c k p o i n t < a − L / 2 index=a-L a − L / 2 ≤ c h e c k p o i n t < a + L / 2 index=a a + L / 2 ≤ c h e c k p o i n t index=a+L \begin{array}{l} a = m*L+seqno-isn \\\\ L = UINT32\_MAX+1 \\\\ \left \{\begin{matrix} checkpoint < a - L/2 & \text{index=a-L}\\\\ a-L/2\leq checkpoint< a+L/2 & \text{index=a}\\\\ a+L/2\leq checkpoint & \text{index=a+L} \end{matrix}\right. \end{array} a = m ∗ L + s e q n o − i s n L = U I N T 3 2 _ M A X + 1 ⎩ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎧ c h e c k p o i n t < a − L / 2 a − L / 2 ≤ c h e c k p o i n t < a + L / 2 a + L / 2 ≤ c h e c k p o i n t index=a-L index=a index=a+L
注:此时checkpoint一定小于 a+L,因为a+L属于第③区间,而checkpoint在第②区间内
seqno - isn < 0此时因为是从m*L
的位置向前移动,所以相比于上面,三个可能是答案的index
的分布则改为了
i n d e x ′ = m ∗ L + s e q n o − i s n i n d e x ′ ′ = i n d e x ′ + L i n d e x ′ ′ ′ = i n d e x ′ + 2 ∗ L index' = m * L + seqno - isn\\ index'' = index' + L\\ index''' = index' + 2 * L i n d e x ′ = m ∗ L + s e q n o − i s n i n d e x ′ ′ = i n d e x ′ + L i n d e x ′ ′ ′ = i n d e x ′ + 2 ∗ L
所以很容易得到以下结果
a = m ∗ L + s e q n o − i s n L = U I N T 32 _ M A X + 1 { c h e c k p o i n t < a + L / 2 index=a a + L / 2 ≤ c h e c k p o i n t < a + ( 3 ∗ L ) / 2 index=a + L a + ( 3 ∗ L ) / 2 ≤ c h e c k p o i n t index=a+2*L \begin{array}{l} a = m*L+seqno-isn\\ L = UINT32\_MAX+1\\ \left \{\begin{matrix} checkpoint < a + L/2 & \text{index=a}\\ a+L/2\leq checkpoint< a+(3*L)/2 & \text{index=a + L}\\ a+(3*L)/2\leq checkpoint & \text{index=a+2*L} \end{matrix}\right. \end{array} a = m ∗ L + s e q n o − i s n L = U I N T 3 2 _ M A X + 1 ⎩ ⎨ ⎧ c h e c k p o i n t < a + L / 2 a + L / 2 ≤ c h e c k p o i n t < a + ( 3 ∗ L ) / 2 a + ( 3 ∗ L ) / 2 ≤ c h e c k p o i n t index=a index=a + L index=a+2*L
将以上两种规律整合,我们很容易可以得到以下通解
a = m ∗ L + s e q n o − i s n L = U I N T 32 _ M A X + 1 { c h e c k p o i n t < a − L / 2 index=a-L a − L / 2 ≤ c h e c k p o i n t < a + L / 2 index=a a + L / 2 ≤ c h e c k p o i n t < a + ( 3 ∗ L ) / 2 index=a + L a + ( 3 ∗ L ) / 2 ≤ c h e c k p o i n t index=a+2*L \begin{array}{l} a = m*L+seqno-isn\\ L = UINT32\_MAX+1\\ \left \{\begin{matrix} checkpoint < a - L/2 & \text{index=a-L}\\ a-L/2\leq checkpoint< a+L/2 & \text{index=a}\\ a+L/2\leq checkpoint< a+(3*L)/2 & \text{index=a + L}\\ a+(3*L)/2\leq checkpoint & \text{index=a+2*L} \end{matrix}\right. \end{array} a = m ∗ L + s e q n o − i s n L = U I N T 3 2 _ M A X + 1 ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ c h e c k p o i n t < a − L / 2 a − L / 2 ≤ c h e c k p o i n t < a + L / 2 a + L / 2 ≤ c h e c k p o i n t < a + ( 3 ∗ L ) / 2 a + ( 3 ∗ L ) / 2 ≤ c h e c k p o i n t index=a-L index=a index=a + L index=a+2*L
特殊情况 checkpoint < L在checkpoint < L
的时候,通解中对于a - L
的一部分(即checkpoint < a + L)就不适用了,不过分析起来也很简单,由于有
( i n d e x + i s n ) m o d L = s e q n o \left ( index+isn \right )\mod{ L } = seqno ( i n d e x + i s n ) m o d L = s e q n o
所以当seqno
小于isn
的时候,答案一定在下一个区间,因此答案即L - isn + seqno
,当seqno
大于isn
且checkpoint < a + L
,所以答案一定为a
所以就可以得到上述代码了。
TCP 段接收处理这部分代码逻辑完成的是tcp
握手中对于tcp
段的接受处理。
我自己增加的私有成员和用途大致为:
_is_syn
: 判断链接是否建立_isn
: 存入第一次建立连接时接受的seqno
来初始化_is_fin
: 用于判断结束输入的报文是否传入对于ackno
和checkpoint
的实现机制是:
ackno
: 本质上就是返回已经整合好的数据量,也就是bytes_stream
的bytes_written()
,同时建立连接后一定存在syn
所以可以直接加一,之后只需要判断fin
是否到达并且整合完毕,然后再次加一即可。checkpoint
: 和ackno
差别不大,只需要直接返回已经写入完成的字符个数即可知道了上述几个逻辑以后就只需要通过调整简单的逻辑flag
加上lab1
里面的push_substring
来对payload()
进行整合就可以通过了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void TCPReceiver::segment_received (const TCPSegment &seg) { if ((_is_syn == 0 ) && seg.header ().syn) { _is_syn = 1 ; _isn = seg.header ().seqno; } else if (_is_syn == 0 ) { return ; } const uint64_t checkpoint = _reassembler.stream_out ().bytes_written () + 1 ; uint64_t stream_index = unwrap (seg.header ().seqno, _isn, checkpoint) - 1 + seg.header ().syn; _reassembler.push_substring (seg.payload ().copy (), stream_index, seg.header ().fin); if (seg.header ().fin) { _is_fin = 1 ; } }optional<WrappingInt32> TCPReceiver::ackno () const { WrappingInt32 result = _isn + _is_syn + _reassembler.stream_out ().bytes_written (); if ((_is_fin != 0 ) && _reassembler.unassembled_bytes () == 0 ) { result = result + _is_fin; } return _is_syn ? optional <WrappingInt32>(result) : nullopt ; }size_t TCPReceiver::window_size () const { return _capacity - _reassembler.stream_out ().buffer_size (); }
这里有一个让我感觉很疑惑的点就是在单元测试中存在两种测试样例,这里做个记录,后面如果知道了原因就来解决一下
存在同时携带SYN
和FIN
报文,按照正常的TCP握手感觉这是不合理的 在接受SYN
的同时会接受一部分的Data
进行处理,按正常的TCP也是不会这么做的