CS144计算机网络 Lab0

一、简介

这里记录了笔者配置 CS144 计算机网络实验环境的一些步骤。

CS144 Lab0 实验指导书 - Lab Checkpoint 0: networking warmup

个人 CS144 实验项目地址 - github

二、环境搭建

如果是使用自己的 Linux 操作系统,照着这个装就好 - BYO Linux installation

不过鉴于目前 Linux 下已经装了不少的东西,因此我这边只需额外再装一个 doxygen + clang-format

1
sudo apt-get install doxygen clang-format

之后下载 CS144 实验包,然后编译

1
2
3
4
5
git clone --recursive git@github.com:Kiprey/sponge.git
mkdir -p sponge/build
cd sponge/build
cmake ..
make

cmake 时可以设置几种编译宏:

1
2
3
4
5
6
-DCMAKE_BUILD_TYPE=Release   # optimizations
-DCMAKE_BUILD_TYPE=Debug # debug symbols and -Og
-DCMAKE_BUILD_TYPE=RelASan # release build with ASan and UBSan
-DCMAKE_BUILD_TYPE=RelTSan # release build with ThreadSan (私以为这个大概率用不到)
-DCMAKE_BUILD_TYPE=DebugASan # debug build with ASan and UBSan
-DCMAKE_BUILD_TYPE=DebugTSan # debug build with ThreadSan

make 也有一些可以用到的编译选项,这里只罗列出比较常用的选项:

1
2
3
4
make doc     # 在 build/doc 中生成本地静态文档,通过 index.html 访问
make format # 使用 CLANG 套件 来格式化代码
make help # 查看全部可make的目标
make check_* # 检测编写的代码

三、代码风格

CS144 使用 C++11 标准完成实验,它对C++代码的风格有着严格的限制:

  • 使用 Resource acquisition is initialization 风格,即 RAII 风格。

  • 禁止使用 malloc 和 free 函数

  • 禁止使用 new 和 delete 关键字

  • 禁止使用原生指针(*)。若有必要,最好使用智能指针(unique_ptr等等)。

    CS144实验指导书说明,该实验没有必要用到指针。

  • 禁止使用模板、线程相关、各类锁机制以及虚函数

  • 禁止使用C风格字符串(char*) 以及 C 风格字符串处理函数。使用 string 来代替。

  • 禁止使用 C 风格强制类型转换。若有必要请使用 static_cast

  • 传递参数给函数时,请使用常量引用类型(const Ty& t)

  • 尽可能将每个变量和函数方法都声明成 const

  • 禁止使用全局变量,以及尽可能让每个变量的作用域最小

  • 在完成代码后,务必使用 make format 来标准化代码风格。

四、尝试手动访问网页

使用 telnet cs144.keithw.org http命令以连接远程网页服务器,之后在终端键入以下内容

内容中的<enter>指的是按下回车符

1
2
3
4
GET /hello HTTP/1.1<enter>
Host: cs144.keithw.org<enter>
Connection: close<enter>
<enter>

之后就可以看到远程服务器将内容正确返回:

image-20211105091331646

比较有意思的是, telnet 在进行 http 访问下,会自动将用户输入的换行转化为 \r\n,而 nc 程序不会这样做。

五、 动手实现一个网络程序

这里我们需要实现一个程序 webget,用于访问外部网页,类似于 wget。

代码量预计 10 行左右,位于apps/webget.cc,实现代码时务必借助 libsponge 中的 TCPSocketAddress 类来完成。

需要注意的是

  • HTTP 头部的每一行末尾都是以\r\n结尾,而不是\n

  • 需要包含Connection: close 的HTTP头部,以指示远程服务器在处理完当前请求后直接关闭。

  • 除非获取到EOF,否则必须循环从远程服务器读取信息。

    因为网络数据的传输可能断断续续,需要多次 read。

这里贴出我的实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 http_tcp;
http_tcp.connect(addr);
http_tcp.write("GET " + path + " HTTP/1.1\r\n");
http_tcp.write("HOST: " + host + "\r\n");
http_tcp.write("Connection: close\r\n");
http_tcp.write("\r\n");

while(!http_tcp.eof())
cout << http_tcp.read();
http_tcp.close();
}

运行的很成功:

image-20211105103200309

六、实现Lab0

Lab0 要求我们实现一个在内存中的 有序可靠字节流(有点类似于管道)

要求

  • 字节流可以从写入端写入,并以相同的顺序,从读取端读取

  • 字节流是有限的,写者可以终止写入。而读者可以在读取到字节流末尾时,产生EOF标志,不再读取。

  • 所实现的字节流必须支持流量控制,以控制内存的使用。当所使用的缓冲区爆满时,将禁止写入操作。直到读者读取了一部分数据后,空出了一部分缓冲区内存,才让写者写入。

  • 写入的字节流可能会很长,必须考虑到字节流大于缓冲区大小的情况。即便缓冲区只有1字节大小,所实现的程序也必须支持正常的写入读取操作。

在单线程环境下执行,因此不用考虑各类条件竞争问题。

这是在内存中的有序可靠字节流,接下来的实验会让我们在不可靠网络中实现一个这样的可靠字节流,而这便是传输控制协议(Transmission Control Protocol,TCP)

以下是实现的代码。

首先是类声明的实现,这里我添加了一个私有变量用以存放一些数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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::deque<char> _queue;
size_t _capacity_size;
size_t _written_size;
size_t _read_size;
bool _end_input;
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;
//!@}
};

具体的成员实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
ByteStream::ByteStream(const size_t capacity)
: _queue(), _capacity_size(capacity), _written_size(0), _read_size(0), _end_input(false), _error(false) {}

size_t ByteStream::write(const string &data) {
if (_end_input)
return 0;
size_t write_size = min(data.size(), _capacity_size - _queue.size());
_written_size += write_size;
for (size_t i = 0; i < write_size; i++)
_queue.push_back(data[i]);
return write_size;
}

//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
size_t pop_size = min(len, _queue.size());
return string(_queue.begin(), _queue.begin() + pop_size);
}

//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
size_t pop_size = min(len, _queue.size());
_read_size += len;
for (size_t i = 0; i < pop_size; i++)
_queue.pop_front();
}

//! 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 data = this->peek_output(len);
this->pop_output(len);
return data;
}

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

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

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

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

bool ByteStream::eof() const { return _end_input && _queue.empty(); }

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

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

size_t ByteStream::remaining_capacity() const { return _capacity_size - _queue.size(); }

这个 Lab0 还是比较简单的,可以看到 check 跑的非常成功:

image-20211107094712926

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2021 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~