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 }
其中,在unwrap方法中,涉及到无符号数减法、无符号数转化为有符号数,有关于计算机组成原理的补码反码等知识,建议了解一下,这里给出示例,用于理解:
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
对应于unwrap方法,offset代表的是checkpoint到n的距离,距离为正代表向正方向移动(超过边界会从0开始),距离为负代表向反方向移动(超过边界会从2 ^ 32 - 1开始)。

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;
tcp_receiver.cc
 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(),严格来讲不应该使用最近到来字节流的字节索引,他有可能还未组装。
以下是实验结果:

 

posted @ 2022-11-03 10:59  愉快的百杰海棠  阅读(370)  评论(0)    收藏  举报