Lab0

本次实验一直在强调的一点就是,TCP的功能是将底层的零散数据包,拼接成一个reliable in-order的byte stream。这个对我来说非常“振聋发聩”(夸张了233),以前只是背诵地知道TCP的可靠性,这次我算是第一次知道了所谓“可靠”究竟可靠在哪:一是保证了序列有序性,二是保证了数据不丢失(从软件层面)。

还有一个就是大致了解了cs144的主题:实现TCP协议。也就是说,运输层下面的那些层是不用管的吗?不过这样也挺恰好,我正好在学校的实验做过对下面这些层的实现了,就差一个TCP23333这样一来,我的协议栈就可以完整了。

本次实验与TCP的关系:

在我们的webget实现中,正是由于TCP的可靠传输,才能使我们的http request正确地被服务器接收,才能使服务器的response正确地被我们接收打印。

而在ByteStream中,我们也做了跟TCP类似的工作:接收substring,并且将它们拼接为in-order的byte stream【由于在内存中/单线程,所以这个工作看起来非常简单】:

1
2
3
4
5
while(is_input_end == false&&pointer<length){
if(buffer.size() == capacity) break;
buffer.push_back(data[pointer]);
pointer++;
}

Fetch a Web page

主要是介绍了telnet指令

屏幕截图 2023-02-23 194758

Send yourself an email

用的是telnet带smtp参

Listening and connecting

上面的telnet是一个client program。接下来我们要把自己放在server的位置上。

用的是netcat指令。

image-20230223202202509

Use socket to write webget

这个确实不难,就是这个地方有点坑:

Please note that in HTTP, each line must be ended with “\r\n” (it’s not sufficient to use just “\n” or endl).

导致我跟400 Bad Request大眼瞪小眼了好久。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void get_URL(const string &host, const string &path) {
TCPSocket sock;
string tmp;
// sock.set_blocking(true);// 默认情况下即为true
sock.connect(Address(host,"http"));
sock.write("GET " + path + " HTTP/1.1\r\nHost: " +
host + "\r\nConnection: close\r\n\r\n");

while((tmp = sock.read(1)) != ""){
cout << tmp;
}
/*
上面那个写法不大规范,更规范的写法:
while(!sock.eof()){
cout << sock.read(1);
}
*/
sock.close();
}

还有一点值得注意的是,当我这样时:

1
2
3
TCPSocket sock;
sock.set_blocking(false);
sock.connect(Address(host,"http"));

会报错Operation now in progress

关于socket通信中在connect()遇到的Operation now in progress错误

遇到此错误是因为将在connect()函数之前将套接字socket设为了非阻塞模式。改为在connect()函数之后设置即可。

我觉得这个实验设计得挺好的,写的时候感觉很有意思。我推荐看下 https://github.com/shootfirst/CS144/blob/main/lab-0/apps/webget.cc 里的注释,写得很好很规范,让我明白了很多本来没搞懂的地方,比如说shutdown的用法。

An in-memory reliable byte stream

实现一个ByteStream类,可以通过readwrite对其两端进行读写。是单线程程序,因而无需考虑阻塞。

感想

这东西其实是很简单的,但是我还是花了一定的时间,主要原因有两点,一是我不懂c++,所以一些地方错得我很懵逼,二是因为我是sb。

下面就记录下三个我印象比较深刻的错误吧。

错误1 member initialization list

构造函数我一开始是这么写的:

image-20230224113108208

结果爆出了这样的错:

image-20230224112056879

搜了半天也没看懂怎么回事,去求助了下某场外c艹选手,才知道了还有成员变量初始化列表这玩意,这个东西似乎比较高效安全。

于是我改成了这么写:

image-20230224113333962

它告诉我buffer也得初始化。于是我又这么写:

image-20230224113358856

又是奇奇怪怪的错误,说明vector不能这么初始化。

场外c艹选手看到了这个:

image-20230224113456432

所以说vector应该这样初始化:

image-20230224113549970

错误2 使用了vector作为buffer的载体

应该使用的是可以从front删除数据的数据结构,比如说deque。【vector也行,但是效率较低】

具体为什么,可以以数据流为cat为例。执行peek(2)时,使用vector得到的是at,使用deque得到的是ca。

错误3 错误地阻塞

一开始在write方法,我是这么写的:

1
2
3
4
5
6
7
int length = data.length();
while(is_input_end == false&&pointer<length){
while(buffer.size() == capacity);
buffer.push_back(data[pointer]);
pointer++;
total_write ++;
}

结果就是测试用例Timeout。我找了很久都不知道错在了哪,最后求助了场外观众【罪过……这次实验太不独立了】,学着他把length改成了这样:

1
int length = min(data.length(),capacity-buffer.size());

发现成了。

我去看了看testbench,猜测应该是因为阻塞了,我还以为是deque自身会阻塞【是的,我完全没注意到自己顺手把阻塞写了下去】,查了半天发现不会,最后才发现是自己不小心搞错了呃呃…………

代码

头文件声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

size_t total_write;
size_t total_read;
bool is_input_end;
const size_t capacity;
deque<char> buffer;

具体实现

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
ByteStream::ByteStream(const size_t cap) : total_write(0),total_read(0),is_input_end(false),capacity(cap),buffer(){ }

//! 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 ByteStream::write(const string &data) {
if(is_input_end == true) is_input_end = false;
int pointer = 0;
int length = data.length();
while(is_input_end == false&&pointer<length){
if(buffer.size() == capacity) break;
buffer.push_back(data[pointer]);
pointer++;
}
total_write+=pointer;
return pointer;
}
//! Peek at next "len" bytes of the stream
//! \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;
size_t i = 0;
for (auto it = buffer.begin(); it != buffer.end(); it++) {
if (i >= len)
break;
i++;
res.push_back(*it);
}
return res;
}

//! Remove bytes from the buffer
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
size_t i;
for (i = 0; i < len; i++) {
if (buffer.empty())
break;
buffer.pop_front();
}
total_read+=i;
}

//! 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() {is_input_end = true;}

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

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

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

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

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

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

size_t ByteStream::remaining_capacity() const { return capacity - buffer.size(); }