TCP协议实现 || CS144 Lab2
【计算机网络】Lab2实验笔记
本文记录在Lab2实现过程中的一些错误记录以及实验心得,CS144实验官方网站:CS 144: Introduction to Computer Networking。本实验基于Lab0、Lab1实验结果:字节流类ByteStream和用于组装字符串的StreamReassembler,接下来,Lab2开搞!预热:64位索引与32位索引转换
-
在StreamReassembler中,它重新组合子字符串,其中每个字节都有一个64位流索引(uint64_t index),流中的第一个字节的索引始终为零。而在TCP标头中,空间是宝贵的,流中每个字节的索引不是用64位索引表示,而是用32位“序列号”或“seqno”表示,流中的第一个序列号是一个随机的32位数字,称为初始序列号(ISN),这是表示SYN(流的开始)的序列号。

- 也就是说,32位索引是循环的,64位我们可以认为是不会溢出,也就是不循环的,我的白话循环是指:一旦32位序列号计数到232− 1,流中的下一个字节将具有序列号0。
- SYN(流开始)和FIN(流结束)控制标志被分配了序列号,每一个都占据一个序列号,如上图所示。
- 请注意:seqno是TCP流中的索引,而StreamReassembler中使用的索引对应上图的stream index。
转换实现:
1 WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
2 return WrappingInt32(static_cast<uint32_t>(n) + isn.raw_value());
3 }
1 uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) { 2 int32_t offset = n.raw_value() - wrap(checkpoint, isn).raw_value(); 3 int64_t result = checkpoint + offset; 4 if(result < 0){ 5 result += 1ul<<32; 6 } 7 return result; 8 }
1 2 ^ 32 = 4294967296
2 uint32_t a = 1;
3 uint32_t b = 0;
4 uint32_t x = a - b;
5 int32_t y = a - b;
6 // // x=1 y=1
7 uint32_t x = b - a;
8 int32_t y = b - a;
9 // // x=4294967295 y=-1
1 uint32_t a = 1; 2 uint32_t b = static_cast<uint32_t>((1UL << 32) - 1UL); 3 uint32_t x = a - b; 4 int32_t y = a - b; 5 // // x=2 y=2 6 uint32_t x = b - a; 7 int32_t y = b - a; 8 // x=4294967294 y=-2
TCPReceiver的主要工作
-
写入发送方发送来的字节流;
-
告诉发送方第一个未组装字节的索引,称为“确认号”或“ackno”(这里是32位的seqno);
-
第一个未组装字节索引和第一个不可接受字节索引之间的距离,称为“窗口大小”(特别注意,这个窗口大小是用来限制发送方的,不是限制自己的);
tcp_receiver.hh
1 class TCPReceiver { 2 StreamReassembler _reassembler; 3 size_t _capacity; 4 bool syn_flag = false; 5 uint32_t isn = 0; 6 uint64_t check_pt = 0;
1 void TCPReceiver::segment_received(const TCPSegment &seg) { 2 auto header = seg.header(); 3 uint64_t abs_seqno = 0; 4 if(header.syn && syn_flag){ 5 return; 6 } 7 if(header.syn){ 8 syn_flag = true; 9 isn = header.seqno.raw_value(); 10 abs_seqno = 1; 11 } 12 else{ 13 abs_seqno = unwrap(header.seqno, WrappingInt32(isn), check_pt); 14 } 15 16 _reassembler.push_substring(seg.payload().copy(), abs_seqno - 1, header.fin); 17 check_pt = stream_out().bytes_written(); 18 }
19 // c++17新特性optional 20 optional<WrappingInt32> TCPReceiver::ackno() const { 21 if(!syn_flag){ 22 return nullopt; 23 } 24 if(stream_out().input_ended()){ 25 return wrap(check_pt, WrappingInt32(isn)) + 2; 26 } 27 return wrap(check_pt, WrappingInt32(isn)) + 1; 28 }
29 // 限制发送方 30 size_t TCPReceiver::window_size() const { 31 return _capacity - stream_out().buffer_size(); 32 }
我踩的坑
- 当时理解错了,对接受到的字节流判断是否超过windows size,其实不同的,我们只需要把收到的字节流塞给组装器,组装器是具有足够的鲁棒性;
- 对于检查点,指导书里说了是最后一个组装字节的索引(absolute seqno),所以应该用stream_out().bytes_written(),严格来讲不应该使用最近到来字节流的字节索引,他有可能还未组装。


浙公网安备 33010602011771号