本实验中,我们将组合TCP sender
和TCP receiver
实现一个完整的TCP connection
TCP
是全双工连接,所以两方可以同时接收/发送信息,一端随时都有可能接收.发送信息
主要根据上图实现
tcp_connection.cc
#include "tcp_connection.hh"
#include
using namespace std;
size_t TCPConnection::remaining_outbound_capacity() const { return _sender.stream_in().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; }
bool TCPConnection::active() const { return _active; }
void TCPConnection::segment_received(const TCPSegment &seg) {
if (!_active)
return;
_time_since_last_segment_received = 0;
// State: closed
if (!_receiver.ackno().has_value() && _sender.next_seqno_absolute() == 0) {
if (!seg.header().syn)
return;
_receiver.segment_received(seg);
connect();
return;
}
// State: syn sent
if (_sender.next_seqno_absolute() > 0 && _sender.bytes_in_flight() == _sender.next_seqno_absolute() &&
!_receiver.ackno().has_value()) {
if (seg.payload().size())
return;
if (!seg.header().ack) {
if (seg.header().syn) {
// simultaneous open
_receiver.segment_received(seg);
_sender.send_empty_segment();
}
return;
}
if (seg.header().rst) {
_receiver.stream_out().set_error();
_sender.stream_in().set_error();
_active = false;
return;
}
}
_receiver.segment_received(seg);
_sender.ack_received(seg.header().ackno, seg.header().win);
// Lab3 behavior: fill_window() will directly return without sending any segment.
// See tcp_sender.cc line 42
if (_sender.stream_in().buffer_empty() && seg.length_in_sequence_space())
_sender.send_empty_segment();
if (seg.header().rst) {
_sender.send_empty_segment();
unclean_shutdown();
return;
}
send_sender_segments();
}
size_t TCPConnection::write(const string &data) {
if (!data.size())
return 0;
size_t write_size = _sender.stream_in().write(data);
_sender.fill_window();
send_sender_segments();
return write_size;
}
//! \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) {
if (!_active)
return;
_time_since_last_segment_received += ms_since_last_tick;
_sender.tick(ms_since_last_tick);
if (_sender.consecutive_retransmissions() > TCPConfig::MAX_RETX_ATTEMPTS)
unclean_shutdown();
send_sender_segments();
}
void TCPConnection::end_input_stream() {
_sender.stream_in().end_input();
_sender.fill_window();
send_sender_segments();
}
void TCPConnection::connect() {
_sender.fill_window();
send_sender_segments();
}
TCPConnection::~TCPConnection() {
try {
if (active()) {
cerr << "Warning: Unclean shutdown of TCPConnection\n";
_sender.send_empty_segment();
unclean_shutdown();
}
} catch (const exception &e) {
std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
}
}
void TCPConnection::send_sender_segments() {
TCPSegment seg;
while (!_sender.segments_out().empty()) {
seg = _sender.segments_out().front();
_sender.segments_out().pop();
if (_receiver.ackno().has_value()) {
seg.header().ack = true;
seg.header().ackno = _receiver.ackno().value();
seg.header().win = _receiver.window_size();
}
_segments_out.push(seg);
}
clean_shutdown();
}
void TCPConnection::unclean_shutdown() {
// When this being called, _sender.stream_out() should not be empty.
_receiver.stream_out().set_error();
_sender.stream_in().set_error();
_active = false;
TCPSegment seg = _sender.segments_out().front();
_sender.segments_out().pop();
seg.header().ack = true;
if (_receiver.ackno().has_value())
seg.header().ackno = _receiver.ackno().value();
seg.header().win = _receiver.window_size();
seg.header().rst = true;
_segments_out.push(seg);
}
void TCPConnection::clean_shutdown() {
if (_receiver.stream_out().input_ended()) {
if (!_sender.stream_in().eof())
_linger_after_streams_finish = false;
else if (_sender.bytes_in_flight() == 0) {
if (!_linger_after_streams_finish || time_since_last_segment_received() >= 10 * _cfg.rt_timeout) {
_active = false;
}
}
}
}
tcp_connection.hh
#ifndef SPONGE_LIBSPONGE_TCP_FACTORED_HH
#define SPONGE_LIBSPONGE_TCP_FACTORED_HH
#include "tcp_config.hh"
#include "tcp_receiver.hh"
#include "tcp_sender.hh"
#include "tcp_state.hh"
//! \brief A complete endpoint of a TCP connection
class TCPConnection {
private:
size_t _time_since_last_segment_received{0};
bool _active{true};
void send_sender_segments();
void clean_shutdown();
void unclean_shutdown();
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 _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};
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(); }
//!@}
//! \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 &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;
//!@}
//! 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;
//!@}
};
#endif // SPONGE_LIBSPONGE_TCP_FACTORED_HH
致此,完成TCP实验