这里回顾CS 144 Lab 0: networking warmup。

实验资料:

Lab 0: networking warmup

2 Networking by hand

2.1

2

telnet cs144.keithw.org http
Trying 104.196.238.229...
Connected to cs144.keithw.org.
Escape character is '^]'.
GET /hello HTTP/1.1
Host: cs144.keithw.org

HTTP/1.1 200 OK
Date: Sat, 13 Nov 2021 03:32:18 GMT
Server: Apache
Last-Modified: Thu, 13 Dec 2018 15:45:29 GMT
ETag: "e-57ce93446cb64"
Accept-Ranges: bytes
Content-Length: 14
Content-Type: text/plain

Hello, CS144!

3

telnet cs144.keithw.org http
GET /lab0/123 HTTP/1.1
Trying 104.196.238.229...
Connected to cs144.keithw.org.
Escape character is '^]'.
Host: cs144.keithw.org


HTTP/1.1 200 OK
Date: Sat, 13 Nov 2021 03:35:44 GMT
Server: Apache
X-You-Said-Your-SunetID-Was: 123
X-Your-Code-Is: 792558
Content-length: 107
Vary: Accept-Encoding
Content-Type: text/plain

Hello! You told us that your SUNet ID was "123". Please see the HTTP headers (above) for your secret code.

2.2

由于没有sunetid,所以无法发送:

telnet smtp-unencrypted.stanford.edu smtp
HELO mycomputer.stanford.edu
MAIL FROM: sunetid@stanford.edu
RCPT TO: sunetid@stanford.edu
DATA
From: sunetid@stanford.edu
To: sunetid@stanford.edu
Subject: Hello from CS144 Lab 0!

hello
.
quit

2.3

netcat:

netcat -v -l -p 9090      
Listening on [0.0.0.0] (family 0, port 9090)                      
Connection from localhost 56596 received!                         
123                                                               
456                                                               
^C                                                                

telnet:

telnet localhost 9090
Trying 127.0.0.1...                                     
Connected to localhost.                                 
Escape character is '^]'.                               
123                                                     
456                                                     
Connection closed by foreign host.                                    

3 Writing a network program using an OS stream socket

准备工作

git clone https://gitee.com/kangyupl/sponge
git checkout -b master origin/master
mkdir build && cd build
cmake ..
make format
make -j4 && make check_lab0

代码

说明:

  • webget是一个使用操作系统的TCP支持和流套接字抽象在Internet上获取网页的程序,就像您在本实验室前面手动完成的一样。

代码位置:

/home/cs144/sponge/apps/webget.cc:

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 address(host, "http");
    TCPSocket socket;
    // 和服务器连接
    socket.connect(address);
    // request
    socket.write("GET " + path + " HTTP/1.1\r\n");
    socket.write("HOST: " + host + "\r\n");
    socket.write("\r\n");
    // request结束
    socket.shutdown(SHUT_WR);
    // content
    while (!socket.eof()) {
        std::cout << socket.read(1);
    }
    // close
    socket.close();
}

测试

测试1,参考2.1:

make && ./apps/webget cs144.keithw.org /hello

结果:

HTTP/1.1 200 OK
Date: Sun, 12 Dec 2021 07:47:48 GMT
Server: Apache
Last-Modified: Thu, 13 Dec 2018 15:45:29 GMT
ETag: "e-57ce93446cb64"
Accept-Ranges: bytes
Content-Length: 14
Content-Type: text/plain

Hello, CS144!

测试2:

make check_webget

结果:

[100%] Testing webget...
Test project /home/cs144/sponge/build
    Start 27: t_webget
1/1 Test #27: t_webget .........................   Passed    0.49 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) =   0.50 sec
[100%] Built target check_webge

4 An in-memory reliable byte stream

说明

到现在为止,你已经看到了可靠的无序字节流的抽象是如何在互联网上进行通信的,尽管互联网本身只提供了”尽力而为”(不可靠)的数据报服务。

为了完成本周的实验,你将在一台计算机的内存中实现一个提供这种抽象的对象。(你可能在CS 110中做过类似的事情。) 字节在 “输入”端写入,并可以从”输出”端以同样的顺序读取。字节流是有限的:writer可以结束输入,然后就不能再写了。当reader读到流的末端时,它将到达”EOF”(结束),不再有更多的字节可以被读取。

你的字节流也会受到控制:它被初始化时有一个特定的容量:它愿意在自己的内存中存储的最大字节数。字节流将限制writer可以写多长的字符串,以确保字节流不会超过其存储容量。当reader读取字节并将其从流中耗尽时,writer被允许写入更多。

你的字节流是在单线程中使用的——你不必担心并发的writer/reader、锁定或竞争条件。

设计

  • 一个缓存区,一端读,一端写,先进先出,这里使用的是list;

代码

byte_stream.hh:

#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH

#include <cstddef>
#include <cstdint>
#include <deque>
#include <list>
#include <string>
#include <utility>

//! \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.
    // list作为缓存
    std::list<char> buf = {};
    size_t size;
    // 0, start, end
    size_t read_cnt = 0;
    size_t write_cnt = 0;
    bool is_end = 0;
    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 vector of bytes read
    std::string read(const size_t len) {
        const auto ret = peek_output(len);
        pop_output(len);
        return ret;
    }

    //! \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

byte_stream.cc:

#include "byte_stream.hh"

#include <algorithm>
#include <iterator>
#include <stdexcept>

// 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`

template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}

using namespace std;

ByteStream::ByteStream(const size_t capacity) : size(capacity) {

}

// write字符, 最多缓存至capacity长度
size_t ByteStream::write(const string &data) {
    int l1 = size - buf.size();
    int l2 = data.size();
    int l = min(l2, l1);
    int i;
    for (i = 0; i < l; i++) {
        buf.push_back(data[i]);
        // 更新计数
        write_cnt++;
    }

    return i;
}

//! \param[in] len bytes will be copied from the output side of the buffer
// 返回buf中前min(len, size)个字符
string ByteStream::peek_output(const size_t len) const {
    string res;
    int l1 = buf.size();
    int l2 = len;
    int l = min(l1, l2);
    int i = 0;
    for (auto it = buf.begin(); (it != buf.end()) && (i < l); i++, it++) {
        res.push_back(*it);
    }

    return res;
}

//! \param[in] len bytes will be removed from the output side of the buffer
// 弹出buf中前min(len, size)个字符
void ByteStream::pop_output(const size_t len) {
    int l1 = buf.size();
    int l2 = len;
    int l = min(l1, l2);
    for (int i = 0; i < l; i++) {
        buf.pop_front();
        // 更新计数
        read_cnt++;
    }
}

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

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

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

bool ByteStream::buffer_empty() const { return buffer_size() == 0; }

// 判断条件
// buffer为空, 并且输入结束
bool ByteStream::eof() const { return buffer_empty() && input_ended(); }

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 size - buf.size(); }

测试

测试:

make -j4 && make check_lab0

结果:

Test project /home/cs144/sponge/build
    Start 22: t_byte_stream_construction
1/9 Test #22: t_byte_stream_construction .......   Passed    0.00 sec
    Start 23: t_byte_stream_one_write
2/9 Test #23: t_byte_stream_one_write ..........   Passed    0.00 sec
    Start 24: t_byte_stream_two_writes
3/9 Test #24: t_byte_stream_two_writes .........   Passed    0.00 sec
    Start 25: t_byte_stream_capacity
4/9 Test #25: t_byte_stream_capacity ...........   Passed    0.00 sec
    Start 26: t_byte_stream_many_writes
5/9 Test #26: t_byte_stream_many_writes ........   Passed    0.01 sec
    Start 27: t_webget
6/9 Test #27: t_webget .........................   Passed    0.43 sec
    Start 47: t_address_dt
7/9 Test #47: t_address_dt .....................   Passed    0.01 sec
    Start 48: t_parser_dt
8/9 Test #48: t_parser_dt ......................   Passed    0.00 sec
    Start 49: t_socket_dt
9/9 Test #49: t_socket_dt ......................   Passed    0.00 sec

100% tests passed, 0 tests failed out of 9

Total Test time (real) =   0.47 sec
[100%] Built target check_lab0