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
改动比较多,就直接贴代码了。
class TCPConnection {
private:
TCPConfig _cfg;
TCPReceiver _receiver{_cfg.recv_capacity};
TCPSender _sender{_cfg.send_capacity, _cfg.rt_timeout, _cfg.fixed_isn};
std::queue<TCPSegment> _segments_out{};
bool _linger_after_streams_finish{true};
bool _isActive{true};
size_t _time_since_last_segment_received{0};
public:
void connect();
size_t write(const std::string &data);
size_t remaining_outbound_capacity() const;
void end_input_stream();
ByteStream &inbound_stream() { return _receiver.stream_out(); }
ByteStream &outbound_stream() { return _sender.stream_in(); }
const ByteStream &outbound_stream() const { return _sender.stream_in(); }
size_t bytes_in_flight() const;
size_t unassembled_bytes() const;
size_t time_since_last_segment_received() const;
TCPState state() const { return {_sender, _receiver, active(), _linger_after_streams_finish}; };
void segment_received(const TCPSegment &seg);
void tick(const size_t ms_since_last_tick);
std::queue<TCPSegment> &segments_out() { return _segments_out; }
bool active() const;
void comeRST(){
outbound_stream().set_error();
inbound_stream().set_error();
_isActive = false;
};
void sendAll();
explicit TCPConnection(const TCPConfig &cfg) : _cfg{cfg} {}
~TCPConnection();
TCPConnection() = delete;
TCPConnection(TCPConnection &&other) = default;
TCPConnection &operator=(TCPConnection &&other) = default;
TCPConnection(const TCPConnection &other) = delete;
TCPConnection &operator=(const TCPConnection &other) = delete;
};
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;
if(seg.header().rst){
comeRST();
return;
}
if(!seg.header().syn && seg.header().ack && !_receiver._isInit){
return;
}
_receiver.segment_received(seg);
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(seg.length_in_sequence_space() && _sender._segments_out.empty()){
_sender.send_empty_segment();
}
sendAll();
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;
}
void TCPConnection::tick(const size_t ms_since_last_tick) {
_sender.tick(ms_since_last_tick);
if(_sender._num_consecutive_retransmissions > TCPConfig::MAX_RETX_ATTEMPTS){
while(!_sender._segments_out.empty())
_sender._segments_out.pop();
_sender.send_empty_segment(true);
sendAll();
comeRST();
}
sendAll();
_time_since_last_segment_received += ms_since_last_tick;
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;
}
_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();
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";
_sender.send_empty_segment(true);
comeRST();
sendAll();
}
} catch (const exception &e) {
std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
}
}
吐了,测试点99死活过不了,服务器收不到seg,一步一步测试到TCP底层代码,还是收不到。最后认为问题不可能出现在要写的部分,然后clone了一个网上的题解,测试还是过不了,然后确定是环境问题。下载了官方提供的ova文件,进行了测试,测试成功!
|