Stanford CS144: Lab 4

1 文档解读

如果本lab中测试点99及之后有问题,考虑是环境问题!!

1.1 Receiving segments

  • 如果RST标志位被设置,把 inbound and outbound streams都设置为error状态,并且永久地kill这个连接。(设置isActive)
  • 解析后发送segment给TCPReceiver
  • 如果ACK 标志位被设置,那么给TCPSender ackno 和 window_size。(没有ACK 那么window_size都不需要传吗?因为第二次握手只有一个SYN标记?)
  • 如果传入的段占用了任何序列号,TCPConnection确保至少发送一个段作为回应,以反映ackno和窗口大小的更新。(占用了任何序列号表示什么?什么情况下是没有占用)

先测试一下 :传输的段可以使得 ack 增加。

1.2 Sending segments

  • 无论何时TCPSender 发送了一个segment,已经设置这个segment的信息:seqno、SYN、payload、FIN。
  • 在发送之前,TCPConnection需要去询问TCPReceiver ackno和window_size的值。如果是一个确认号ackno,设置ACK标志位。(连接的时候没有ACK)
  • 尽可能地发送segment,即在sender._segment_out中有信息的时候就写入 connection.__segment_out.

回顾sender中写入_segment_out的情况:

  • 向stream中写入完毕后
  • ackReceived后
  • close后
  • 测试类初始化时
  • tick超时

1.3 Time passes

TCPConnection有一个tick方法会被OS周期性调用。当tick被调用时:

  • 通知TCPSender,多长时间过去了。
  • 如果连续重传次数大于最大次数,关闭连接,并发送一个RST标志位被设置的空segment给对方。
  • 如果有必要就结束连接(见1.5)

1.4 FAQ

  • tick()调用和TCPSender类似。
  • 收到设置RST的segment:
    • inbound and outbound ByteStreams的状态设置为error
    • 之后调用TCPConnection::active()都返回false
  • 什么时候发送RST:
    • 重传次数超限
    • 当active()返回true,TCPConnection的析构函数被调用时
  • 发送RST和接受RST一样,设置error状态,然后active()返回也为false。
  • RST segment的seq如何确定?
    • 发送正确的seq的空的segment
    • 或者你也可以通过调用fill_window()去填满window(如果有数据需要进行发送,来自stream 或者 SYN/FIN)
  • ACK标志位的作用?所有的segment都有吗?
    • 几乎所有的segment都是确认帧,除了第一次握手。
    • 对于outgoing segments,任何时候,你都需要设置ackno和ACK
    • 对于ingoing segments,只有当ACK被设置时,你才需要去看。
  • 如果TCPReceiver想要一个更大的window_size,如何去设置?
    • 尽可能大的值。std::numeric_limits class 会有帮助。
  • 什么时候已经TCP连接结束?什么时候 active()会return false?
    • 见下一节

1.5 The end of a TCP connection

当TCPConnection 决定TCP连接结束之后,会释放一个端口号,停止发送确认帧,active()返回false。

有两种方法使得连接结束。

unclean shutdown:接收或者发送RST。这种情况下 ByteStream的状态是error,active()返回false。

clean shutdown:是一种没有error但active() = false的情况。这表示实现了完整可靠地发送以及接收。由于”Two Generals Problem“,我们不可能实现一个 clean shutdown,但是TCP有一个精彩的close方法。从某一方TCPConnection的视角来看,实现clean shutdown 有四个必备条件:

  • inbound stream完全排列完,并达到ended。
  • outbound stream的数据完全发送出去。
  • outbound stream的数据完全被对方所确认。
  • local peer 需要认为remote peer已经完成了第三个要求。有两种方式可以使得TCPConnection知道对方已经完全确认。

方式一:流传送结束后等待

因为不会传送可靠的对确认帧的确认帧,所以local peer不能明确知道remote peer是否完成了全部流的接收。

但是local peer可以通过等待一段时间,来观察remote peer是否会重新发送任何信息来判断对方是否完全接收了。lab中采用的等待时间是10个initial retransmission。(一般应用中应该是2个MSL,报文在网络中传输的最大时间)

方式二:被动关闭

对方是先结束stream的情况。

1.5.1 The end of a TCP connection (practical summary)

在实践的角度中,用一个linger_after_streams_finish 变量来处理。起始值为true。如果 inbound stream 在 outbound stream EOF之前end,这个变量设置为false。

(方式二)在任何时间点,满足1,3条件,且 linger_after_streams_finish = false,这个连接就结束(active() = false)。否则就需要等待(方式一)。

2 Debug

不能用get去拿stl成员

2.1 空确认帧是不需要回复的。

通过seg.length_in_sequence_space() 去判断是否是。

2.2 大于index + window_size的data丢弃(还未处理一半在里面的)

2.3 未完成握手,不进行回复

可以回复确认帧。

2.4 在SYN发送给对方后,对方有可能只回复ACK,不回复SYN

3 Code

改动比较多,就直接贴代码了。

// tcp_connection.hh
class TCPConnection {
  private:
    TCPConfig _cfg;
    TCPReceiver _receiver{_cfg.recv_capacity};
    TCPSender _sender{_cfg.send_capacity, _cfg.rt_timeout, _cfg.fixed_isn};

    //! outbound queue of segments that the TCPConnection wants sent
    std::queue<TCPSegment> _segments_out{};

    //! Should the TCPConnection stay active (and keep ACKing)
    //! for 10 * _cfg.rt_timeout milliseconds after both streams have ended,
    //! in case the remote TCPConnection doesn't know we've received its whole stream?
    bool _linger_after_streams_finish{true};

    bool _isActive{true};

    size_t _time_since_last_segment_received{0};

  public:
    //! \name "Input" interface for the writer
    //!@{

    //! \brief Initiate a connection by sending a SYN segment
    void connect();

    //! \brief Write data to the outbound byte stream, and send it over TCP if possible
    //! \returns the number of bytes from `data` that were actually written.
    size_t write(const std::string &data);

    //! \returns the number of `bytes` that can be written right now.
    size_t remaining_outbound_capacity() const;

    //! \brief Shut down the outbound byte stream (still allows reading incoming data)
    void end_input_stream();
    //!@}

    //! \name "Output" interface for the reader
    //!@{

    //! \brief The inbound byte stream received from the peer
    ByteStream &inbound_stream() { return _receiver.stream_out(); }
    ByteStream &outbound_stream() { return _sender.stream_in(); }
    const ByteStream &outbound_stream() const { return _sender.stream_in(); }
    //!@}

    //! \name Accessors used for testing

    //!@{
    //! \brief number of bytes sent and not yet acknowledged, counting SYN/FIN each as one byte
    size_t bytes_in_flight() const;
    //! \brief number of bytes not yet reassembled
    size_t unassembled_bytes() const;
    //! \brief Number of milliseconds since the last segment was received
    size_t time_since_last_segment_received() const;
    //!< \brief summarize the state of the sender, receiver, and the connection
    TCPState state() const { return {_sender, _receiver, active(), _linger_after_streams_finish}; };
    //!@}

    //! \name Methods for the owner or operating system to call
    //!@{

    //! Called when a new segment has been received from the network
    void segment_received(const TCPSegment &seg);

    //! Called periodically when time elapses
    void tick(const size_t ms_since_last_tick);

    //! \brief TCPSegments that the TCPConnection has enqueued for transmission.
    //! \note The owner or operating system will dequeue these and
    //! put each one into the payload of a lower-layer datagram (usually Internet datagrams (IP),
    //! but could also be user datagrams (UDP) or any other kind).
    std::queue<TCPSegment> &segments_out() { return _segments_out; }

    //! \brief Is the connection still alive in any way?
    //! \returns `true` if either stream is still running or if the TCPConnection is lingering
    //! after both streams have finished (e.g. to ACK retransmissions from the peer)
    bool active() const;
    //!@}

    //! when send or receive a RST,set the state.
    void comeRST(){
        outbound_stream().set_error();
        inbound_stream().set_error();
        _isActive = false;
    };

    //! Send the segments in _sender._segments_out
    void sendAll();

    //! Construct a new connection from a configuration
    explicit TCPConnection(const TCPConfig &cfg) : _cfg{cfg} {}

    //! \name construction and destruction
    //! moving is allowed; copying is disallowed; default construction not possible

    //!@{
    ~TCPConnection();  //!< destructor sends a RST if the connection is still open
    TCPConnection() = delete;
    TCPConnection(TCPConnection &&other) = default;
    TCPConnection &operator=(TCPConnection &&other) = default;
    TCPConnection(const TCPConnection &other) = delete;
    TCPConnection &operator=(const TCPConnection &other) = delete;
    //!@}
};
// tcp_connection.cc
size_t TCPConnection::remaining_outbound_capacity() const { return outbound_stream().remaining_capacity(); }

size_t TCPConnection::bytes_in_flight() const { return _sender.bytes_in_flight(); }

size_t TCPConnection::unassembled_bytes() const { return _receiver.unassembled_bytes(); }

size_t TCPConnection::time_since_last_segment_received() const { return _time_since_last_segment_received; }

void TCPConnection::segment_received(const TCPSegment &seg) {
    _time_since_last_segment_received = 0;

    // RST set
    if(seg.header().rst){
        comeRST();
        return;
    }

    // Ignore ACK,when in LISTEN.
    if(!seg.header().syn && seg.header().ack && !_receiver._isInit){
        return;
    }


    //! Test the case: the segment will increment the receiver's expt_seq
    _receiver.segment_received(seg);

    // Set the _linger_after_streams_finish.
    if(inbound_stream().get_end_input_flag() && !outbound_stream().eof())
        _linger_after_streams_finish = false;


    if(seg.header().ack){
        _sender.ack_received(seg.header().ackno,seg.header().win);
    }
    _sender.fill_window();

    // If no segment to be sent,send an empty one.
    if(seg.length_in_sequence_space() && _sender._segments_out.empty()){
        _sender.send_empty_segment();
    }

    // Add the ackno and window_size on the segment to be sent.
    sendAll();

    // Passive close
    if(!_linger_after_streams_finish && inbound_stream().get_end_input_flag() && outbound_stream().eof() && _sender._next_seqno == _sender._ack_seqno){
        _isActive = false;
    }

}

bool TCPConnection::active() const { return _isActive; }

size_t TCPConnection::write(const string &data) {

    size_t ret = outbound_stream().write(data);
    _sender.fill_window();
    sendAll();
    if(!_linger_after_streams_finish && inbound_stream().get_end_input_flag() && outbound_stream().eof() && _sender._next_seqno == _sender._ack_seqno){
        _isActive = false;
    }
    return ret;
}

//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method
void TCPConnection::tick(const size_t ms_since_last_tick) {
    _sender.tick(ms_since_last_tick);

    // Exceed the limit,abort the connection.
    if(_sender._num_consecutive_retransmissions > TCPConfig::MAX_RETX_ATTEMPTS){
        while(!_sender._segments_out.empty())
            _sender._segments_out.pop();
        _sender.send_empty_segment(true);
        sendAll();

        // Set the local peer state.
        comeRST();
    }

    // Send the TCPSegments.
    sendAll();

    _time_since_last_segment_received += ms_since_last_tick;


    // Clean shutdown.
    if(_time_since_last_segment_received >= 10 * _cfg.rt_timeout &&  inbound_stream().get_end_input_flag() && outbound_stream().eof() && _sender._next_seqno == _sender._ack_seqno){
        _isActive = false;
    }
}

void TCPConnection::end_input_stream() {
    outbound_stream().end_input();
    _sender.fill_window();
    sendAll();
    if(!_linger_after_streams_finish && inbound_stream().get_end_input_flag() && outbound_stream().eof() && _sender._next_seqno == _sender._ack_seqno){
        _isActive = false;
    }
}

void TCPConnection::connect() {
    _sender.fill_window();
    if(_sender._segments_out.size() != 1){
        return;
    }

    //! There is no need to send window_size and ack?
    _segments_out.push(_sender._segments_out.front());
    _sender._segments_out.pop();
}

void TCPConnection::sendAll(){
    while(!_sender._segments_out.empty()){
        _sender._segments_out.front().header().ackno = wrap(_receiver.getExpt_seq(),_receiver.get_isn());
        _sender._segments_out.front().header().win = _receiver.window_size();
        // Set ACK flag
        if(_receiver.getExpt_seq() != 0)
            _sender._segments_out.front().header().ack = true;
        _segments_out.push(_sender._segments_out.front());
        _sender._segments_out.pop();
    }
}

TCPConnection::~TCPConnection() {
    try {
        if (active()) {
            cerr << "Warning: Unclean shutdown of TCPConnection\n";

            // Your code here: need to send a RST segment to the peer
            _sender.send_empty_segment(true);
            // Set the local peer state.
            comeRST();

            // Send the TCPSegments.
            sendAll();
        }
    } catch (const exception &e) {
        std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
    }
}

吐了,测试点99死活过不了,服务器收不到seg,一步一步测试到TCP底层代码,还是收不到。最后认为问题不可能出现在要写的部分,然后clone了一个网上的题解,测试还是过不了,然后确定是环境问题。下载了官方提供的ova文件,进行了测试,测试成功!

Stanford CS144: Lab 4_第1张图片

你可能感兴趣的:(LAB)