前言

  • merge lab4的初始代码,出现如下变化

image.png

进入build文件夹
运行make -j4
然后依次检查lab0~lab3的结果,由于加入了新的样例,所以前面的实验可能由没通过的新的样例

DEBUG LAB0~LAB3

  • LAB1样例没过,DEBUG过程见LAB1实现的第五阶段
  • LAB3样例没过,DEBUG过程见LAB3实现的第三阶段

第一阶段

分析

  • 新增成员
    • bool _rst,如果收到RST报文段或者发送RST报文段,则_rst为true

疑问

  • 终止连接需要做什么事情?

TCPConnection和TCPSender一样有一个_segments_out,我要做的事情是不是:从TCPSender中的_segments_out取出TCPSegment,添加ackno和window size字段,然后放到TCPConnection的_segments_out中,这个操作应该放在哪里?write方法吗?

  • write方法
  • segment_received方法,确保返回一个带着ack的段
  • end_input_stream方法

ACK和window size字段是配套的吗?如果没有ack,还需要设置window size吗?

如何利用之前的接口发送一个空的RST报文段?

在哪里判断连续重传次数是否达到上限?tick()

什么时候连接会正常关闭?连接正常关闭需要做什么事情?如何进入lingering状态?

image.png这样对que操作,不会修改_sender.segments_out的内容

TCPConnection::end_input_stream,也需要fill_window,以发送FIN
image.png

断开连接时,本地先发送FIN,对面的FIN和ACK可能会在一个报文段中传回来
image.png

什么时候会关闭入流?什么时候会关闭出流?什么时候将_linger_after_streams_finish置为false

  • 关闭出流:TCPConnection::end_input_stream()
  • 关闭入流:_receiver._reassembler::push_substring()
    • 这个方法运行到,如果所有的字节已经组装完成(unassembled_bytes()==0&&_next_index==_eof_index)
    • 那么就调用_output.end_input()

TCPConnection关闭之后,还能从里面读出数据吗?我觉得是可以的,只要数据已经被组装完成,什么时候取走都没关系

假如说先收到对方发送的FIN,但是前面还有一些数据报文段没到(乱序或丢失),然后在对面发过来的数据字节流完全组装完成之前,我先发送了FIN,那么这种情况到底算谁主动发起关闭连接?
换一种说法,我先发送FIN,还没收到这个FIN的ACK之前,对方也发了FIN过来,那么我是否需要lingering?如下图
image.png

在TCPConnection::end_input_stream()中查看_receiver.stream_out().input_ended(),如果是true,说明已经收到对面FIN,且已经被组装

如果_sender数据已经全部发完,但是还可能重传,这个时候出流算不算结束?

我们在学习四次挥手的时候,那个图是画的4个报文段,这种情况是一种比较理想的情况;实际上现实中的TCP连接在断开的时候,并不一定是这么理想的,双方可能在几乎相同的时间给对方发送FIN报文段,这种情况双方最后都会进入lingering状态。
如下图是理想状况
image.png
如下图是B将ACK和FIN合并
image.png
如下图是两者几乎同时发送FIN报文段,在这种情况下,我认为两者都会进入lingering状态
image.png

_linger_after_streams_finish**初始化为true。如果入站流在TCPConnection的出站流读到EOF之前结束(意思就是对方先发送FIN),则此变量需要设置为false




TCPConnection不一定是主动发起连接的一方,如果先收到对方的SYN报文段,我也要回复SYN+ACK报文段,所以在
TCPConnection::segment_received中也需要调用_sender.fill_window()

image.png

代码

tcp_connection.hh

  1. #ifndef SPONGE_LIBSPONGE_TCP_FACTORED_HH
  2. #define SPONGE_LIBSPONGE_TCP_FACTORED_HH
  3. #include "tcp_config.hh"
  4. #include "tcp_receiver.hh"
  5. #include "tcp_sender.hh"
  6. #include "tcp_state.hh"
  7. //! \brief A complete endpoint of a TCP connection
  8. class TCPConnection {
  9. private:
  10. TCPConfig _cfg;
  11. TCPReceiver _receiver{_cfg.recv_capacity};
  12. TCPSender _sender{_cfg.send_capacity, _cfg.rt_timeout, _cfg.fixed_isn};
  13. //! outbound queue of segments that the TCPConnection wants sent
  14. std::queue<TCPSegment> _segments_out{};
  15. //! Should the TCPConnection stay active (and keep ACKing)
  16. //! for 10 * _cfg.rt_timeout milliseconds after both streams have ended,
  17. //! in case the remote TCPConnection doesn't know we've received its whole stream?
  18. bool _linger_after_streams_finish{true};
  19. bool _rst{false};
  20. size_t do_send(bool limit=false, size_t send_num=0);
  21. bool _lingering{false};
  22. size_t _linger_timer_ms{0};
  23. bool _closed{false};
  24. size_t _time_since_last_segment_received{0};
  25. public:
  26. //! \name "Input" interface for the writer
  27. //!@{
  28. // 下面的4个接口是应用程序在使用这个TCPConnection,用来发送数据的接口
  29. //! \brief Initiate a connection by sending a SYN segment
  30. void connect();
  31. //! \brief Write data to the outbound byte stream, and send it over TCP if possible
  32. //! \returns the number of bytes from `data` that were actually written.
  33. size_t write(const std::string &data);
  34. //! \returns the number of `bytes` that can be written right now.
  35. size_t remaining_outbound_capacity() const;
  36. //! \brief Shut down the outbound byte stream (still allows reading incoming data)
  37. void end_input_stream();
  38. //!@}
  39. //! \name "Output" interface for the reader
  40. //!@{
  41. // 这个就是TCPReceiver组装后的字节流
  42. //! \brief The inbound byte stream received from the peer
  43. ByteStream &inbound_stream() { return _receiver.stream_out(); }
  44. //!@}
  45. //! \name Accessors used for testing
  46. // 下面的4个接口是用于校验测试结果的
  47. //!@{
  48. //! \brief number of bytes sent and not yet acknowledged, counting SYN/FIN each as one byte
  49. size_t bytes_in_flight() const;
  50. //! \brief number of bytes not yet reassembled
  51. size_t unassembled_bytes() const;
  52. //! \brief Number of milliseconds since the last segment was received
  53. size_t time_since_last_segment_received() const;
  54. //!< \brief summarize the state of the sender, receiver, and the connection
  55. TCPState state() const { return {_sender, _receiver, active(), _linger_after_streams_finish}; };
  56. //!@}
  57. //! \name Methods for the owner or operating system to call
  58. //!@{
  59. // 下面的5个接口是操作系统调用的
  60. // 网络层收到packet,转化成TCPSegment,调用这个函数提交给传输层
  61. //! Called when a new segment has been received from the network
  62. void segment_received(const TCPSegment &seg);
  63. //! Called periodically when time elapses
  64. // 时间的流逝
  65. void tick(const size_t ms_since_last_tick);
  66. //! \brief TCPSegments that the TCPConnection has enqueued for transmission.
  67. //! \note The owner or operating system will dequeue these and
  68. //! put each one into the payload of a lower-layer datagram (usually Internet datagrams (IP),
  69. //! but could also be user datagrams (UDP) or any other kind).
  70. // 操作系统会从这个队列中拿出数据,放到网络层并发送到网络上
  71. std::queue<TCPSegment> &segments_out() { return _segments_out; }
  72. //! \brief Is the connection still alive in any way?
  73. //! \returns `true` if either stream is still running or if the TCPConnection is lingering
  74. //! after both streams have finished (e.g. to ACK retransmissions from the peer)
  75. // 连接是否active
  76. bool active() const;
  77. //!@}
  78. //! Construct a new connection from a configuration
  79. explicit TCPConnection(const TCPConfig &cfg) : _cfg{cfg} {}
  80. //! \name construction and destruction
  81. //! moving is allowed; copying is disallowed; default construction not possible
  82. // 下面是构造函数和析构函数
  83. //!@{
  84. ~TCPConnection(); //!< destructor sends a RST if the connection is still open
  85. TCPConnection() = delete;
  86. TCPConnection(TCPConnection &&other) = default;
  87. TCPConnection &operator=(TCPConnection &&other) = default;
  88. TCPConnection(const TCPConnection &other) = delete;
  89. TCPConnection &operator=(const TCPConnection &other) = delete;
  90. //!@}
  91. };
  92. #endif // SPONGE_LIBSPONGE_TCP_FACTORED_HH

tcp_connection.cc

  1. #include "tcp_connection.hh"
  2. #include <iostream>
  3. // Dummy implementation of a TCP connection
  4. // For Lab 4, please replace with a real implementation that passes the
  5. // automated checks run by `make check`.
  6. template <typename... Targs>
  7. void DUMMY_CODE(Targs &&... /* unused */) {}
  8. using namespace std;
  9. //! \returns the number of `bytes` that can be written right now.
  10. // 返回outgoing bytestream的剩余容量
  11. // _sender.stream_in()
  12. size_t TCPConnection::remaining_outbound_capacity() const { return _sender.stream_in().remaining_capacity(); }
  13. //! \brief number of bytes sent and not yet acknowledged, counting SYN/FIN each as one byte
  14. // 返回已经发送但未确认的字节数
  15. // _sender.bytes_in_flight()
  16. size_t TCPConnection::bytes_in_flight() const { return _sender.bytes_in_flight(); }
  17. //! \brief number of bytes not yet reassembled
  18. // 返回已经接收但未组装的字节数
  19. // _receiver.unassembled_bytes()
  20. size_t TCPConnection::unassembled_bytes() const { return _receiver.unassembled_bytes(); }
  21. //! \brief Number of milliseconds since the last segment was received
  22. // 返回自接收到上个报文段经过的毫秒数
  23. size_t TCPConnection::time_since_last_segment_received() const { return _time_since_last_segment_received; }
  24. //! Called when a new segment has been received from the network
  25. // 当网络层收到packet后,会转化为TCPSegment,调用这个函数
  26. /*
  27. 如果设置了RST(重置)标志,将入站和出站的流都设置为错误状态并永久终止连接。否则……
  28. 将报文段提供给TCPReceiver,以便它可以检查其关注的字段:seqno、SYN、payload和FIN
  29. 如果ACK标志已设置,告诉TCPSender它关心的字段:ackno和接收窗口大小。
  30. 如果传入的段占用任何序列号,则TCPConnection确保至少发送一个段作为答复,以反映ackno和窗口大小的更新。
  31. */
  32. void TCPConnection::segment_received(const TCPSegment &seg) {
  33. _time_since_last_segment_received=0;
  34. if (_closed||_rst) {
  35. return;
  36. }
  37. if (seg.header().rst) { // 如果设置了RST标志
  38. _sender.stream_in().set_error();
  39. _receiver.stream_out().set_error();
  40. _rst=true;
  41. } else {
  42. // 交给receiver处理
  43. _receiver.segment_received(seg);
  44. if (seg.header().ack) { // 如果设置了ACK标志
  45. // 告诉TCPSender它关心的字段:ackno和接收窗口大小
  46. _sender.ack_received(seg.header().ackno, seg.header().win);
  47. }
  48. // 如果收到了SYN报文,那么这里的调用可以回复SYN报文
  49. if (_receiver.ackno().has_value()&&!_receiver.stream_out().input_ended()) {
  50. _sender.fill_window();
  51. }
  52. if (seg.length_in_sequence_space()) { // 如果传入的段占用任何序列号
  53. // std::cout<<"****** TCPConnection::segment_received with seqno space occupied>0, at least one ack should be send"<<std::endl;
  54. // 则TCPConnection确保至少发送一个段作为答复,以反映ackno和窗口大小的更新
  55. if (!do_send()) { // 如果没有发送任何段,则发送一个空的ACK报文段
  56. // std::cout<<"****** TCPConnection::segment_received generate a empty ACK segment"<<std::endl;
  57. _sender.send_empty_segment();
  58. do_send();
  59. }
  60. }
  61. /* 如果当前收到的报文段带FIN标志,那么_receiver.segment_received(seg)处理完后,
  62. _receiver.stream_out.input_ended()可能已经是true,表示入流已经结束,
  63. 上面的代码确保发送一个ACK,这个ACK有可能就是对这个FIN的ACK,
  64. 此时需要检查出流是否也已经结束
  65. */
  66. /* 如果当前收到的报文段带ack,那么ACK有可能是对我之前发送的FIN的ACK,这里也需要检查出流和入流是否已经全部结束
  67. */
  68. // std::cout<<"****** instream ended: "<<_receiver.stream_out().input_ended()
  69. // <<", outstream ended:"<<(_sender.stream_in().eof() && _sender.next_seqno_absolute()==_sender.stream_in().bytes_written()+2 && _sender.bytes_in_flight()>0)<<std::endl;
  70. // _stream.eof()&&_next_seqno==_stream.bytes_written()+2&&bytes_in_flight()==0
  71. if (_receiver.stream_out().input_ended()&&!_sender.stream_in().eof()) {
  72. _linger_after_streams_finish=false;
  73. }
  74. if (_receiver.stream_out().input_ended()&&(
  75. _sender.stream_in().eof() && _sender.next_seqno_absolute()==_sender.stream_in().bytes_written()+2 &&
  76. _sender.bytes_in_flight()==0
  77. )) {
  78. if (_linger_after_streams_finish) {
  79. if (!_lingering) {
  80. // std::cout<<"****** TCPConnection into lingering"<<std::endl;
  81. _lingering=true;
  82. }
  83. } else {
  84. // std::cout<<"****** TCPConnection closed"<<std::endl;
  85. _closed=true;
  86. }
  87. }
  88. }
  89. }
  90. //! \brief Is the connection still alive in any way?
  91. //! \returns `true` if either stream is still running or if the TCPConnection is lingering
  92. //! after both streams have finished (e.g. to ACK retransmissions from the peer)
  93. bool TCPConnection::active() const {
  94. return !_rst&&!_closed;
  95. // if (_rst||_closed) {
  96. // return false;
  97. // }
  98. // // _sender.stream_in(), _receiver.stream_out()
  99. // // 如何判断正在lingering?
  100. // // if (_sender.stream_in().eof()&&_receiver.stream_out().eof()&&!_linger_after_streams_finish) {
  101. // if (_sender.stream_in().eof()&&_receiver.stream_out().input_ended()&&!_linger_after_streams_finish) {
  102. // return false;
  103. // }
  104. // return true;
  105. }
  106. //! \brief Write data to the outbound byte stream, and send it over TCP if possible
  107. //! \returns the number of bytes from `data` that were actually written.
  108. size_t TCPConnection::write(const string &data) {
  109. size_t sz = _sender.stream_in().write(data);
  110. _sender.fill_window();
  111. do_send();
  112. return sz;
  113. }
  114. // 从TCPSender的_segments_out中取出TCPSegment,添加ack和window size字段,放入TCPConnection的_segments_out中
  115. size_t TCPConnection::do_send(bool limit, size_t send_num) {
  116. size_t cnt=0;
  117. // auto que = _sender.segments_out(); // 这么写是复制了一个队列出来,不会更改原队列
  118. while (_sender.segments_out().size()&&!(limit&&cnt<send_num)) {
  119. cnt++;
  120. TCPSegment seg = _sender.segments_out().front();
  121. _sender.segments_out().pop();
  122. std::optional<WrappingInt32> ackno=_receiver.ackno();
  123. if (ackno) {
  124. seg.header().ack=true;
  125. seg.header().ackno=ackno.value();
  126. size_t window_size=_receiver.window_size();
  127. // 需要转化为16位
  128. uint16_t uint16_window_size=0xFFFF&window_size;
  129. seg.header().win=uint16_window_size;
  130. }
  131. if (_rst) {
  132. seg.header().rst=true;
  133. }
  134. // (A=1,R=0,S=0,F=0,ackno=1,payload_size=0,)
  135. // std::cout<<"****** TCPConnection do send a segment(A="<<seg.header().ack
  136. // <<",R="<<seg.header().rst<<",S="<<seg.header().syn
  137. // <<",F="<<seg.header().fin<<",payload_size="<<seg.payload().size()<<")"<<std::endl;
  138. _segments_out.push(seg);
  139. }
  140. // std::cout<<"****** TCPConnection do send "<<cnt<<" segments and "<<_sender.segments_out().size()<<" left in _send._segments_out"<<std::endl;
  141. return cnt;
  142. }
  143. //! \param[in] ms_since_last_tick number of milliseconds since the last call to this method
  144. void TCPConnection::tick(const size_t ms_since_last_tick) {
  145. _time_since_last_segment_received+=ms_since_last_tick;
  146. if (_rst||_closed) {
  147. return;
  148. }
  149. if (_lingering) {
  150. _linger_timer_ms+=ms_since_last_tick;
  151. // std::cout<<"****** Lingering, _linger_timer_ms: "<<_linger_timer_ms<<", up to: "<<10*_cfg.rt_timeout;
  152. // std::cout<<", _closed: "<<_closed<<", linger_after_streams_finish: "<<_linger_after_streams_finish;
  153. // std::cout<<", active():"<<active()<<std::endl;
  154. if (_linger_timer_ms>=10*_cfg.rt_timeout) {
  155. _lingering=false;
  156. _closed=true;
  157. // std::cout<<"****** TCPConnection lingering ended, now fully closed"<<std::endl;
  158. }
  159. return; // 我认为如果在lingering,说明_sender已经没有东西要发送了,也没有需要重传的东西
  160. }
  161. // 如果在lingering,这里后面的代码还需要执行吗?
  162. _sender.tick(ms_since_last_tick);
  163. if (_sender.consecutive_retransmissions()>TCPConfig::MAX_RETX_ATTEMPTS) { // 这段代码应该在do_send之前
  164. _sender.stream_in().set_error();
  165. _receiver.stream_out().set_error();
  166. _rst=true;
  167. }
  168. do_send(); // 需要发送重传的报文段
  169. }
  170. //! \brief Shut down the outbound byte stream (still allows reading incoming data)
  171. void TCPConnection::end_input_stream() {
  172. // std::cout<<"****** Call TCPConnection::end_intput_stream()"<<std::endl;
  173. // if (_receiver.stream_out().input_ended()) { // 这个判断条件对应了_recevier的FIN_RECEIVED状态
  174. // std::cout<<"****** in stream has ened, _linger_after_streams_finish set false"<<std::endl;
  175. // _linger_after_streams_finish=false;
  176. // }
  177. _sender.stream_in().end_input();
  178. // 如果对方的接收窗口大小不够,这里调用fill_window还不一定能发送FIN
  179. _sender.fill_window(); // fixed
  180. do_send();
  181. }
  182. //! \brief Initiate a connection by sending a SYN segment
  183. // 初始化连接,发送一个SYN报文段
  184. // 需要保证这个函数只能被成功调用一次吧?
  185. void TCPConnection::connect() {
  186. // std::cout<<"****** TCPConnection::connect called";
  187. // 主动发起连接,发送SYN报文
  188. _sender.fill_window();
  189. // std::cout<<", _sender.segments_out().size()"<<_sender.segments_out().size();
  190. // if (do_send()==1) {
  191. // std::cout<<"****** send syn when connect"<<std::endl;
  192. // }
  193. do_send();
  194. // std::cout<<", TCPConnection._segments_out.size()"<<_segments_out.size()<<std::endl;
  195. }
  196. //!< destructor sends a RST if the connection is still open
  197. TCPConnection::~TCPConnection() {
  198. try {
  199. if (active()) {
  200. cerr << "Warning: Unclean shutdown of TCPConnection\n";
  201. _rst=true;
  202. _sender.stream_in().set_error();
  203. _receiver.stream_out().set_error();
  204. if (!do_send(true, 1)) {
  205. _sender.send_empty_segment();
  206. do_send(true, 1);
  207. }
  208. // Your code here: need to send a RST segment to the peer
  209. }
  210. } catch (const exception &e) {
  211. std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
  212. }
  213. }

测试结果

每次测试都会有若干个测试样例没过,而且最骚的是每次错的样例编号还不一样
image.png
image.png
image.pngimage.png

DEBUG

  • tcp_receiver和tcp_sender的文档都说了,在这两个实验中,不需要set_error
  • 调试半天不出不知道问题在哪里,躺在床上心如死灰,半睡半醒中忽然想起文档中的这句话,然后起来把tcp_receiver中的这行代码注释掉,就100%样例都过了

image.png

最终代码

说明

  • 和第一阶段相比,只是去掉了tcp_receiver.cc中的stream_out().set_error()这行代码,所以tcp_connection.cc和tcp_connecion.hh的逻辑都没有任何变化
  • 另外把调试输出都去掉了

测试结果

运行了很多次,偶尔会出现1个样例的timeout,这个先不管了,就当过了

LAB4剩余部分

命令行测试

流程见LAB4翻译的测试一节,测试通过,指定-w 1也没有问题

性能测试

image.png

改写webget

image.png

总结

  • 虽然TCP连接会有各种状态的演化,但是在代码中,并没有显式定义这些状态
  • 例如,如果照本宣科的按照4次挥手的状态变化来写代码,很可能无法涵盖一些特殊情况,如下图,双方都会进入lingering状态

image.png

  • 全程可以说没有参考别人的代码,自己在一周的时间内通过了LAB0~LAB4的所有样例
  • 通过这些测试样例并不是终点,后面复习TCP知识点的时候还可以结合这里的代码来强化记忆
  • 后面的实验有时间再做