CS144 Lab 0: networking warmup()

Lab0 主要完成两部分, 第一部分为补全 webget.cc 中的函数 get_url, 第二部分为实现一个字节流byte_stream.

Writing webget

在根据 2.1 的提示输入指令后, 终端中会显示 “Hello, CS144!”. 这一部分我们需要做的就是通过函数 get_url 模拟 2.1 的流程. 实验文档中也给出了需要阅读的类的提示, 在Documentation 中找到对应的类, 观察可以发现具体的使用方式. 理论上只需要阅读 TCPConnection 类和 Address 类. 但是由于继承关系, 最好把它们的父类也看一下.

我们需要一个地址, 然后与这个地址建立一个TCP连接, 连接建立完成后开始发送数据请求, 将收到的回复输出, 关闭连接. 根据这个思路可以写出代码.

void get_URL(const string &host, const string &path) {
    // Your code here.

    // You will need to connect to the "http" service on
    // the computer whose name is in the "host" string,
    // then request the URL path given in the "path" string.

    // Then you'll need to print out everything the server sends back,
    // (not just one call to read() -- everything) until you reach
    // the "eof" (end of file).

    Address addr(host, "http");

    TCPSocket TCPConnection;
    TCPConnection.connect(addr);

    string str = "GET /hello HTTP/1.1\r\nHost: cs144.keithw.org\r\nConnection: close\r\n\r\n";

    TCPConnection.write(str);

    while (!TCPConnection.eof()) {
        cout << TCPConnection.read();
    }

    TCPConnection.close();

    cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
    cerr << "Warning: get_URL() has not been implemented yet.\n";
}

回车需要用\r\n表示, 并且在请求末尾需要一个额外的\r\n. 在实际的测试后, 终端正常输出, 但是却提示“ERROR: webget returned output that did not match the test’s expectations”, 可能是操作系统不同的原因, 我没有使用课程提供的系统, 而是用的ubuntu20.04 server版本.

An in-memory reliable byte stream

代码中的注释和实验文档中对与相关函数的解释都非常明确, 这里就不在多记述. 在写之前, 我们需要明确的一点, 那就是我们应该选择一个什么样的数据结构去实现这个字节流. 根据它的描述, 这应该是一个类似循环队列的数据结构, 可以不停的读和写. 最开始我想使用char* 和两个游标去实现这个字节流, 但是根据课程的提示, 不能用new、 malloc之类函数, 最终使用了std::string.

在阅读函数说明时, 我们应该尽量保证函数的复用性, 减少功能的冗余, 例如read函数实际就是peek_out和pop_out的组合. 根据函数的返回值, 我们可以大概确定这个函数的功能, 例如函数void ByteStream::end_input() 和 bool ByteStream::input_ended() const. 可以想到第一个的返回值为空, 那么它应该是一个状态设置, 而第二个函数的返回值为bool类型, 它应该是一个获取状态函数.

#include "byte_stream.hh"

// Dummy implementation of a flow-controlled in-memory byte stream.

// For Lab 0, please replace with a real implementation that passes the
// automated checks run by `make check_lab0`.

// You will need to add private members to the class declaration in `byte_stream.hh`

using namespace std;

ByteStream::ByteStream(const size_t capacity) : _buffer(""), _capacity(capacity), _read_cnt(0), _write_cnt(0), _eof(false) {}

size_t ByteStream::write(const string &data) {
    string temp_data = data;
    size_t write_byte;
    if (_buffer.size() + data.size() > _capacity) {
        temp_data = data.substr(0, _capacity - _buffer.size());
        write_byte = _capacity - _buffer.size();
        _buffer += temp_data;
        
    } else {
        write_byte = data.size();
        _buffer += temp_data;
    }
    _write_cnt += write_byte;
    return write_byte;
}

//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
    string res = _buffer.substr(0, len);
    return res;
}

//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
    _buffer = _buffer.substr(len);
    _read_cnt += len;
 }

//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \param[in] len bytes will be popped and returned
//! \returns a string
std::string ByteStream::read(const size_t len) {
    string res = peek_output(len);
    pop_output(len);
    return res;
}

void ByteStream::end_input() {
    _eof = true;
}

bool ByteStream::input_ended() const { 
    return _eof;
 }

size_t ByteStream::buffer_size() const { 
    return _buffer.size();
 }

bool ByteStream::buffer_empty() const {
    return _buffer.empty();
 }

bool ByteStream::eof() const {
    return (_eof && _buffer.empty());
 }

size_t ByteStream::bytes_written() const {
    return _write_cnt;
}

size_t ByteStream::bytes_read() const {
    return _read_cnt;
}

size_t ByteStream::remaining_capacity() const {
    return (_capacity - _buffer.size());
}
#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH

#include <string>

//! \brief An in-order byte stream.

//! Bytes are written on the "input" side and read from the "output"
//! side.  The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
class ByteStream {
  private:
    // Your code here -- add private members as necessary.

    // Hint: This doesn't need to be a sophisticated data structure at
    // all, but if any of your tests are taking longer than a second,
    // that's a sign that you probably want to keep exploring
    // different approaches.

    std::string _buffer;

    size_t _capacity;

    size_t _read_cnt;
  
    size_t _write_cnt;

    bool _eof;

    bool _error{};  //!< Flag indicating that the stream suffered an error.

  public:
    //! Construct a stream with room for `capacity` bytes.
    ByteStream(const size_t capacity);

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

    //! Write a string of bytes into the stream. Write as many
    //! as will fit, and return how many were written.
    //! \returns the number of bytes accepted into the stream
    size_t write(const std::string &data);

    //! \returns the number of additional bytes that the stream has space for
    size_t remaining_capacity() const;

    //! Signal that the byte stream has reached its ending
    void end_input();

    //! Indicate that the stream suffered an error.
    void set_error() { _error = true; }
    //!@}

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

    //! Peek at next "len" bytes of the stream
    //! \returns a string
    std::string peek_output(const size_t len) const;

    //! Remove bytes from the buffer
    void pop_output(const size_t len);

    //! Read (i.e., copy and then pop) the next "len" bytes of the stream
    //! \returns a string
    std::string read(const size_t len);

    //! \returns `true` if the stream input has ended
    bool input_ended() const;

    //! \returns `true` if the stream has suffered an error
    bool error() const { return _error; }

    //! \returns the maximum amount that can currently be read from the stream
    size_t buffer_size() const;

    //! \returns `true` if the buffer is empty
    bool buffer_empty() const;

    //! \returns `true` if the output has reached the ending
    bool eof() const;
    //!@}

    //! \name General accounting
    //!@{

    //! Total number of bytes written
    size_t bytes_written() const;

    //! Total number of bytes popped
    size_t bytes_read() const;
    //!@}
};

#endif  // SPONGE_LIBSPONGE_BYTE_STREAM_HH
————————

Lab0 主要完成两部分, 第一部分为补全 webget.cc 中的函数 get_url, 第二部分为实现一个字节流byte_stream.

Writing webget

在根据 2.1 的提示输入指令后, 终端中会显示 “Hello, CS144!”. 这一部分我们需要做的就是通过函数 get_url 模拟 2.1 的流程. 实验文档中也给出了需要阅读的类的提示, 在Documentation 中找到对应的类, 观察可以发现具体的使用方式. 理论上只需要阅读 TCPConnection 类和 Address 类. 但是由于继承关系, 最好把它们的父类也看一下.

我们需要一个地址, 然后与这个地址建立一个TCP连接, 连接建立完成后开始发送数据请求, 将收到的回复输出, 关闭连接. 根据这个思路可以写出代码.

void get_URL(const string &host, const string &path) {
    // Your code here.

    // You will need to connect to the "http" service on
    // the computer whose name is in the "host" string,
    // then request the URL path given in the "path" string.

    // Then you'll need to print out everything the server sends back,
    // (not just one call to read() -- everything) until you reach
    // the "eof" (end of file).

    Address addr(host, "http");

    TCPSocket TCPConnection;
    TCPConnection.connect(addr);

    string str = "GET /hello HTTP/1.1\r\nHost: cs144.keithw.org\r\nConnection: close\r\n\r\n";

    TCPConnection.write(str);

    while (!TCPConnection.eof()) {
        cout << TCPConnection.read();
    }

    TCPConnection.close();

    cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
    cerr << "Warning: get_URL() has not been implemented yet.\n";
}

回车需要用\r\n表示, 并且在请求末尾需要一个额外的\r\n. 在实际的测试后, 终端正常输出, 但是却提示“ERROR: webget returned output that did not match the test’s expectations”, 可能是操作系统不同的原因, 我没有使用课程提供的系统, 而是用的ubuntu20.04 server版本.

An in-memory reliable byte stream

代码中的注释和实验文档中对与相关函数的解释都非常明确, 这里就不在多记述. 在写之前, 我们需要明确的一点, 那就是我们应该选择一个什么样的数据结构去实现这个字节流. 根据它的描述, 这应该是一个类似循环队列的数据结构, 可以不停的读和写. 最开始我想使用char* 和两个游标去实现这个字节流, 但是根据课程的提示, 不能用new、 malloc之类函数, 最终使用了std::string.

在阅读函数说明时, 我们应该尽量保证函数的复用性, 减少功能的冗余, 例如read函数实际就是peek_out和pop_out的组合. 根据函数的返回值, 我们可以大概确定这个函数的功能, 例如函数void ByteStream::end_input() 和 bool ByteStream::input_ended() const. 可以想到第一个的返回值为空, 那么它应该是一个状态设置, 而第二个函数的返回值为bool类型, 它应该是一个获取状态函数.

#include "byte_stream.hh"

// Dummy implementation of a flow-controlled in-memory byte stream.

// For Lab 0, please replace with a real implementation that passes the
// automated checks run by `make check_lab0`.

// You will need to add private members to the class declaration in `byte_stream.hh`

using namespace std;

ByteStream::ByteStream(const size_t capacity) : _buffer(""), _capacity(capacity), _read_cnt(0), _write_cnt(0), _eof(false) {}

size_t ByteStream::write(const string &data) {
    string temp_data = data;
    size_t write_byte;
    if (_buffer.size() + data.size() > _capacity) {
        temp_data = data.substr(0, _capacity - _buffer.size());
        write_byte = _capacity - _buffer.size();
        _buffer += temp_data;
        
    } else {
        write_byte = data.size();
        _buffer += temp_data;
    }
    _write_cnt += write_byte;
    return write_byte;
}

//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
    string res = _buffer.substr(0, len);
    return res;
}

//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
    _buffer = _buffer.substr(len);
    _read_cnt += len;
 }

//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \param[in] len bytes will be popped and returned
//! \returns a string
std::string ByteStream::read(const size_t len) {
    string res = peek_output(len);
    pop_output(len);
    return res;
}

void ByteStream::end_input() {
    _eof = true;
}

bool ByteStream::input_ended() const { 
    return _eof;
 }

size_t ByteStream::buffer_size() const { 
    return _buffer.size();
 }

bool ByteStream::buffer_empty() const {
    return _buffer.empty();
 }

bool ByteStream::eof() const {
    return (_eof && _buffer.empty());
 }

size_t ByteStream::bytes_written() const {
    return _write_cnt;
}

size_t ByteStream::bytes_read() const {
    return _read_cnt;
}

size_t ByteStream::remaining_capacity() const {
    return (_capacity - _buffer.size());
}
#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH

#include <string>

//! \brief An in-order byte stream.

//! Bytes are written on the "input" side and read from the "output"
//! side.  The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
class ByteStream {
  private:
    // Your code here -- add private members as necessary.

    // Hint: This doesn't need to be a sophisticated data structure at
    // all, but if any of your tests are taking longer than a second,
    // that's a sign that you probably want to keep exploring
    // different approaches.

    std::string _buffer;

    size_t _capacity;

    size_t _read_cnt;
  
    size_t _write_cnt;

    bool _eof;

    bool _error{};  //!< Flag indicating that the stream suffered an error.

  public:
    //! Construct a stream with room for `capacity` bytes.
    ByteStream(const size_t capacity);

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

    //! Write a string of bytes into the stream. Write as many
    //! as will fit, and return how many were written.
    //! \returns the number of bytes accepted into the stream
    size_t write(const std::string &data);

    //! \returns the number of additional bytes that the stream has space for
    size_t remaining_capacity() const;

    //! Signal that the byte stream has reached its ending
    void end_input();

    //! Indicate that the stream suffered an error.
    void set_error() { _error = true; }
    //!@}

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

    //! Peek at next "len" bytes of the stream
    //! \returns a string
    std::string peek_output(const size_t len) const;

    //! Remove bytes from the buffer
    void pop_output(const size_t len);

    //! Read (i.e., copy and then pop) the next "len" bytes of the stream
    //! \returns a string
    std::string read(const size_t len);

    //! \returns `true` if the stream input has ended
    bool input_ended() const;

    //! \returns `true` if the stream has suffered an error
    bool error() const { return _error; }

    //! \returns the maximum amount that can currently be read from the stream
    size_t buffer_size() const;

    //! \returns `true` if the buffer is empty
    bool buffer_empty() const;

    //! \returns `true` if the output has reached the ending
    bool eof() const;
    //!@}

    //! \name General accounting
    //!@{

    //! Total number of bytes written
    size_t bytes_written() const;

    //! Total number of bytes popped
    size_t bytes_read() const;
    //!@}
};

#endif  // SPONGE_LIBSPONGE_BYTE_STREAM_HH