CS144-Lab0 计算机网络:流的输入和读出

本文最后更新于:2022年12月1日 凌晨

热身

lab0前后分为两个较为简单的小任务,第一个任务是写一个类似telnet中通信的webget小应用,第二个任务是实现一个简单的ByteStream的类,只需要在单线程的情况下能正常运行即可

任务一

第一个任务的参考主要是从项目文件本身的doctests开始着手,其中在提示中已经说了我们将会使用到TCPSocketAddress,在对应的doctests/socket_example_2.ccdoctests/address_example_1.cc中,我们可以得到对于他们的使用例子,只需要创建一个以目标Address初始化并连接的TCPSocket,然后以这个socket向目标服务器发送类似telnet的请求即可获得我们需要的内容

1
2
3
4
5
TCPSocket socket;
socket.connect(Address(host, "http"));
socket.write("GET " + path + " HTTP/1.1\r\n");
socket.write("Host: " + host + "\r\n");
socket.write("Connection: close\r\n\r\n");

由于最后在输入完Connection: close之后,我们本来也要输入一个回车将请求发送,因此在这里需要两个换行符

在处理socket.read()的时候,起初没有仔细考虑pdf中提到的a single call to read is not enough的具体含义,以为是首先会接受所有的文本信息,然后对于结果需要将最后的EOF也打印出来,所以第一次写的时候只是简单的调用了两次read()

1
cout << socket.read() << socket.read();

然而在make check_webget的时候并没有通过,为了找到问题所在,首先找到check_webget的脚本,发现测试的内容为对cs144.keithw.org下的接口/nph-hasher/xyzzy发送请求,并获取最后一行的内容,而这行内容应该是一串正确的HASH。但是在这个时候尝试以上文的方式运行webget的时候则发现输出的只有以下两行

1
2
HTTP/1.1 200 OK
Content-type: text/plain

但是通过telnet的情况下,正常的输入应该是以下的内容

1
2
3
4
HTTP/1.1 200 OK
Content-type: text/plain

7SmXqWkrLKzVBCEalbSPqBcvs11Pw263K7x4Wv3JckI

这个时候就懂了多次调用read()的含义直到遇到eof的含义应该是一直读取到所有缓冲区内的内容都被读取完毕,将代码修改如下就可以通过测试了

1
2
3
4
5
6
7
8
9
10
11
void get_URL(const string &host, const string &path) {
TCPSocket socket;
socket.connect(Address(host, "http"));
socket.write("GET " + path + " HTTP/1.1\r\n");
socket.write("Host: " + host + "\r\n");
socket.write("Connection: close\r\n\r\n");
while (!socket.eof()) {
cout << socket.read();
}
socket.close();
}

任务二

第二个任务主要是要我们自己根据头文件的内容来实现一个简单的ByteStream,并且只需要考虑单线程的情况,不用考虑并发等情况。

最后完成的答案先直接贴上来,这块难度也不会很大,在写的时候先大致按要求写出一个逻辑,即使不是很清楚具体实现对不对也问题不大,只需要通过调试逐步修改即可

首先添加的成员变量如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ByteStream {
private:
// buffer capacity
const size_t buffer_max_size;

// buffer string
std::string buffer;

// input ending flag
bool is_input_end = false;

// counter
size_t write_count, read_count;

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

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

public:
...

最后接口的实现如下

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
ByteStream::ByteStream(const size_t capacity) : buffer_max_size(capacity), buffer(), write_count(0), read_count(0) {}

size_t ByteStream::write(const string &data) {
// 不需要在这里判断input_ended,因为写入过程都是单线程,如果要ended肯定是在write之后进行的
size_t cnt = min(remaining_capacity(), data.length());
buffer += data.substr(0, cnt);
write_count += cnt;
return cnt;
}

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

//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
buffer.erase(0, len);
read_count += 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 output = buffer.substr(0, len);
pop_output(len);
// 在pop_output的时候会计算读取,这里不需要再+=len
return output;
}

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

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

size_t ByteStream::buffer_size() const { return buffer.length(); }

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

bool ByteStream::eof() const { return input_ended() && buffer.empty(); }

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

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

size_t ByteStream::remaining_capacity() const { return buffer_max_size - buffer.length(); }

调试

接口的逻辑实现都不难,不过写这个lab的时候的第一次在vscode的环境下使用cmake来进行调试,在这里简单记录一下调试的步骤和需求。

相关code插件: CMake, CMake Tools, C/C++(Cpptools)

在这里以write()函数中缺少了write_count += cnt这一行为例,使用make check_lab0可以发现在最后有报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
56% tests passed, 4 tests failed out of 9

Total Test time (real) = 1.49 sec

The following tests FAILED:
27 - t_byte_stream_one_write (Failed)
28 - t_byte_stream_two_writes (Failed)
29 - t_byte_stream_capacity (Failed)
30 - t_byte_stream_many_writes (Failed)
Errors while running CTest
make[3]: *** [CMakeFiles/check_lab0.dir/build.make:71: CMakeFiles/check_lab0] Error 8
make[2]: *** [CMakeFiles/Makefile2:228: CMakeFiles/check_lab0.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:235: CMakeFiles/check_lab0.dir/rule] Error 2
make: *** [Makefile:160: check_lab0] Error 2

此时可以将注意力先集中在最上面的t_byte_stream_one_write上,cmake中将byte_stream_one_write作为target编译并运行,可以得到以下报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Test Failure on expectation:
Expectation: bytes_written: 3

Failure message:
The ByteStream should have had bytes_written equal to 3 but instead it was 0

List of steps that executed successfully:
Initialized with (capacity=15)
Action: write "cat" to the stream
Expectation: input_ended: 0
Expectation: buffer_empty: 0
Expectation: eof: 0
Expectation: bytes_read: 0

Exception: The test "write-end-pop" failed

从这里可以知道是测试"write-end-pop"中的第五个执行中,bytes_written并没有返回预期希望的数字3。因此只需要在tests/byte_stream_one_write.cc内"write-end-pop"的test.execute(BytesWritten{3});的位置打上断点,然后直接使用vscode下方栏中cmakedebug图标(需要安装C/C++插件才可以使用快速调试,具体参考这里)就可以逐步找到自己逻辑中出错的地方并修改即可。

热身的两个lab提供的成就感很足,希望自己能尽快完成下一个lab


CS144-Lab0 计算机网络:流的输入和读出
https://halc.top/p/2ca0860a
作者
HalcyonAzure
发布于
2022年10月29日
许可协议