简介: 非常建议做之前认真阅读指导书以及回顾之前写过些什么内容!!!
代码的合并在指导书上说的很详细了,不再赘述。
lab3 是要让我们实现 TCP 发送的功能 ,而 minnow 版的 lab4 则是在现实世界中用 ping 来测量 RTT,相较于 sponge 版,它其实是将 sponge 的 lab3 和 lab4 做了一定程度的整合,以至于我做起来非常痛苦。
另外,学网络如果光写代码确实也缺乏了网络学习中的乐趣,没有实际解决网络问题的成就感,而 minnow 版的这个现实世界结合没啥意思,所以我十分推荐将 lab4 替换为以下链接里的抓包实战,做完一定会觉得分析网络是一件很有意思的事情。
计算机网络实用技术:序章
甚至我觉得抓包分析就应该作为计算机网络的期末考试,而不是去默写背诵一堆又臭又长的概念…
抑或是半期考试考什么书上 Alice 和 Bob 通信时监听中间人的名字是什么…
lab-3: 做完抓包分析对整个网络有了更深理解后,我们再来看我们需要实现的内容。
到目前为止,我们已经实现了:
现在只要实现发送方,并再做一定整合我们就可以完成 tcp 协议栈了。
1 2 客户端 = TCP发送方 + TCP接收方 + 连接管理 服务端 = TCP发送方 + TCP接收方 + 连接管理
实现: 磨了很久,中间 debug 不出来问了好几次 ai。里面的细节有些多,并且在本 lab 中的测试用例让我倒回去改了 lab1 中的代码(现在已经在对应 lab1 中修改了)。
这里前几个函数的实现思路可以参考 bytestream,例如这里的有多少 seuqnce_numbers_in_flight 或者 consecutive_retransmissions 其实都只是一个状态,就和 bytestream 中 is_connect 是一个道理。所以在私有成员中维护一个值返回即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 uint64_t TCPSender::sequence_numbers_in_flight () const { debug ( "unimplemented sequence_numbers_in_flight() called" ); return bytes_in_flight_; }uint64_t TCPSender::consecutive_retransmissions () const { debug ( "unimplemented consecutive_retransmissions() called" ); return retransmissions_; }
一些细节: 这里面另外需要注意的点是,syn 与 fin 的处理, syn 设置后,装的数据里要为它留一个位置,同样的,如果 fin 设置 装不下也需要在下一个包里填上去。
以及窗口的大小一定是从 1 开始的。
1 uint64_t window_size = window_size_ > 0 ? window_size_ : 1 ;
重传状态记录,用队列是方便带时间标记,有点类似于写 bfs 算法题。
1 outstanding_segments_.push ({msg, 0 });
以及只有在成功确认新段时才能重置 RTO,而不是在每次收到 ACK 时
1 2 3 4 if (segments_acknowledged) { retransmissions_ = 0 ; RTO_ms_ = initial_RTO_ms_; }
这里面还使用了回调函数:
算是很有意思的处理(不过也有可能是因为我写的代码不多的原因)。
最重要的是!!!一定要在每个可能发送数据的地方都要检查流错误,测试用例里面有很多针对这个地方的点。
1 2 3 4 if (msg.RST) { writer ().set_error (); return ; }
完整代码:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 #include "tcp_sender.hh" #include "debug.hh" #include "tcp_config.hh" using namespace std;uint64_t TCPSender::sequence_numbers_in_flight () const { return bytes_in_flight_; }uint64_t TCPSender::consecutive_retransmissions () const { return retransmissions_; }void TCPSender::push ( const TransmitFunction& transmit ) { if (reader ().has_error ()) { TCPSenderMessage msg; msg.seqno = isn_ + next_seqno_; msg.RST = true ; transmit (msg); return ; } uint64_t window_size = window_size_ > 0 ? window_size_ : 1 ; uint64_t abs_ackno = ackno_.has_value () ? ackno_.value ().unwrap (isn_, next_seqno_) : 0 ; uint64_t window_end = abs_ackno + window_size; uint64_t available_window = window_end > next_seqno_ ? window_end - next_seqno_ : 0 ; if (next_seqno_ == 0 && available_window > 0 ) { TCPSenderMessage msg; msg.seqno = isn_; msg.SYN = true ; size_t payload_capacity = available_window > 1 ? available_window - 1 : 0 ; payload_capacity = std::min (payload_capacity, static_cast <uint64_t >(TCPConfig::MAX_PAYLOAD_SIZE)); if (payload_capacity > 0 && (reader ().peek ().size ()>0 )) { size_t read_size = std::min (payload_capacity, reader ().peek ().size ()); if (read_size > 0 ) { msg.payload = reader ().peek ().substr (0 , read_size); reader ().pop (read_size); } } if (reader ().is_finished () && !fin_sent_ && available_window > 1 + msg.payload.size ()) { msg.FIN = true ; fin_sent_ = true ; } transmit (msg); size_t segment_size = 1 + msg.payload.size () + (msg.FIN ? 1 : 0 ); outstanding_segments_.push ({msg, 0 }); bytes_in_flight_ += segment_size; next_seqno_ += segment_size; if (!timer_running_) { timer_running_ = true ; time_since_last_activity_ = 0 ; RTO_ms_ = initial_RTO_ms_; } available_window -= segment_size; if (msg.FIN) { return ; } } while (available_window > 0 ){ if (reader ().peek ().size () == 0 && (!reader ().is_finished () || fin_sent_)) { break ; } TCPSenderMessage msg; msg.seqno = isn_ + next_seqno_; size_t payload_size = std::min (available_window,static_cast <uint64_t >(TCPConfig::MAX_PAYLOAD_SIZE)); payload_size = std::min (payload_size,reader ().peek ().size ()); if (payload_size > 0 ){ msg.payload = reader ().peek ().substr (0 ,payload_size); reader ().pop (payload_size); } if (reader ().is_finished () && !fin_sent_ && available_window > msg.payload.size ()) { msg.FIN = true ; fin_sent_ = true ; } if (msg.payload.empty () && !msg.SYN && !msg.FIN) { break ; } transmit (msg); size_t segment_size = msg.payload.size () + (msg.FIN ? 1 : 0 ); outstanding_segments_.push ({msg, 0 }); bytes_in_flight_ += segment_size; next_seqno_ += segment_size; if (!timer_running_) { timer_running_ = true ; time_since_last_activity_ = 0 ; RTO_ms_ = initial_RTO_ms_; } available_window -= segment_size; if (msg.FIN) { break ; } } return ; }TCPSenderMessage TCPSender::make_empty_message () const { TCPSenderMessage msg; msg.seqno = isn_ + next_seqno_; if (reader ().has_error ()) { msg.RST = true ; } return msg; }void TCPSender::receive (const TCPReceiverMessage& msg) { if (msg.RST) { writer ().set_error (); return ; } window_size_ = msg.window_size; if (msg.ackno.has_value ()){ uint64_t abs_ackno = msg.ackno.value ().unwrap (isn_,next_seqno_); if (abs_ackno <= next_seqno_){ ackno_ = msg.ackno; bool segments_acknowledged = false ; while (!outstanding_segments_.empty ()){ const auto & segment = outstanding_segments_.front (); uint64_t seg_seqno = segment.msg.seqno.unwrap (isn_,next_seqno_); uint64_t seg_end = seg_seqno + segment.msg.payload.size () + (segment.msg.SYN ? 1 : 0 ) + (segment.msg.FIN ? 1 : 0 ); if (seg_end <= abs_ackno) { bytes_in_flight_ -= (segment.msg.payload.size () + (segment.msg.SYN ? 1 : 0 ) + (segment.msg.FIN ? 1 : 0 )); outstanding_segments_.pop (); segments_acknowledged = true ; } else { break ; } } if (segments_acknowledged) { retransmissions_ = 0 ; RTO_ms_ = initial_RTO_ms_; if (!outstanding_segments_.empty ()) { time_since_last_activity_ = 0 ; } else { timer_running_ = false ; } } } } }void TCPSender::tick ( uint64_t ms_since_last_tick, const TransmitFunction& transmit ) { if (reader ().has_error ()) { TCPSenderMessage msg; msg.seqno = isn_ + next_seqno_; msg.RST = true ; transmit (msg); return ; } if (timer_running_) { time_since_last_activity_ += ms_since_last_tick; if (time_since_last_activity_ >= RTO_ms_ && !outstanding_segments_.empty ()) { transmit (outstanding_segments_.front ().msg); if (window_size_ > 0 ) { retransmissions_++; RTO_ms_ *= 2 ; } time_since_last_activity_ = 0 ; } } }
测试结果:
应用: 按照指导书上的教程最后成功实现了互通。以及 webget 也测试成功了。还是很有趣的。这里可以好好体验一下抓包的乐趣 。
总结: 建议好好阅读提供的框架代码, tcp_ipv4.cc 实现了一个基于 TCP/IPv4 的网络应用程序框架,用于创建可以在 TUN 虚拟网络接口上运行的 TCP 客户端和服务器。
这里原理和 clash 等代理软件有类似之处。不过 cpp 的那些特性实在太难看了,没心情继续看了 : (