CS144-0

CS144-0

由于没有学校的邮箱,前面虚拟机交流也很简单,直接从代码发送阶段开始。

1.编写报文
实验步骤:

在你的虚拟机上,获取实验的源代码: git clone https://github.com/cs144/minnow

进入 Lab 0 目录: cd minnow

创建一个目录来编译实验室软件: cmake -S . -B build

编译源代码: cmake --build build

在构建目录外部,打开并开始编辑 writeups/check 0. Md 文件。

在文件 ../apps/webget. cc 中,找到并实现 get_URL 函数。你需要使用 HTTP 协议,使用 TCPSocket 和 Address 类。
这里会出现的问题是克隆GitHub库的时候,由于网络问题克隆不了,建议多尝试,不推荐直接从GitHub库下载后导入虚拟机,因为后续需要同步新的实验,会由于git库的归属问题,导致无法同步。

做这一部分以前,要先有Http报文的前置知识,了解整个http的报文结构,才能完成这一部分的实验

知识点

1.1发送get报文
在这个实验中,只需要发送最简单的GET报文请求即可,即写一段字符串,包含GET,HTTP类型,Host接口,以及最后的连接关闭。

这部分代码并不难,我会放在文章的末尾,在此之前如果你只是过来看思路,可以自己先思考一下,需要注意的是每一个元素之间需要有一个空格以及最后也需要一个换行,细节上需要去留意。

之后我们在控制台make我们的代码

测试
通过运行 ./apps/webget cs144.keithw.org /hello 来测试程序。你可以实验不同的 http URL。比如说 ./apps/webget www.baidu.com /hello

自动化测试
然后运行 cmake --build build --target check_webget 进行自动测试。

当看到两个测试用例通过,代表你可以继续下一步的实验了,打开 src/byte stream.hhsrc/byte stream.cc 文件,实现给定接口的对象。

2.实现字节系统
我提供一些简单的接口描述

void push( std::string data ); // 将数据推入流中,但只推入可用容量的数据。

void close(); // 表示流已经结束,不会有更多数据写入。; // 将数据推入流中,但只推入可用容量的数据。

void set_error(); // 表示流出现错误。

bool is_closed() const; // 判断流是否已关闭。

uint64_t available_capacity() const; // 当前可以推入流的字节数。

uint64_t bytes_pushed() const; // 已推入流的总字节数。

# 读取者接口:

void pop( uint64_t len ); // 从缓冲区移除 len 字节。

bool is_finished() const; // 判断流是否已完成(关闭并完全弹出)。

bool has_error() const; // 判断流是否发生过错误。

uint64_t bytes_buffered() const; // 当前缓冲的字节数量(推入但尚未弹出的)。

uint64_t bytes_popped() const; // 已从流中弹出的总字节数。

这一部分我总体的实现思路是,直接通过原生的string作为buffer,然后通过设置一些变量在头文件中记录状态,比如poped函数就可以直接返回这些状态。具体代码我会放在文末,或者您可以访问我的GitHub,来获得代码。

然后通过使用 cmake --build build --target check0 命令运行自动化测试。

代码

webget.cc

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
#include "socket.hh"
#include <vector>
#include <cstdlib>
#include <iostream>
#include <random>
#include <span>
#include <string>

using namespace std;
vector<string>MoreRequest;
void get_URL( const string& host, const string& path )
{
Address addr(host,"http");
TCPSocket tcp_socket;//建立套接字,用来连接(有了插坐)
try{
tcp_socket.connect(addr);
}
catch ( const exception& e ) {
cerr << "Unable to connect:"<<e.what() << "\n";
return ;
}
string DefaultRequest="GET";
string ConnectionHead = "Close";
string CacheControlHead="no-cache";
if(!MoreRequest.empty()){
for(string &str:MoreRequest){
CacheControlHead+=(","+str);
}
}
string RequestHead(
DefaultRequest+" "+path+ " HTTP/1.1\r\n" );
RequestHead+="Host: " + host+ "\r\n";
RequestHead+="Cache-Control: " + CacheControlHead + "\r\n";
RequestHead+="Connection: " + ConnectionHead + "\r\n\r\n";
tcp_socket.write(RequestHead);
string outinfo;
tcp_socket.read( outinfo );
while ( !outinfo.empty() ) {
cout << outinfo;
tcp_socket.read( outinfo );
}
tcp_socket.close();
}


int main( int argc, char* argv[] )
{
try {
if ( argc <= 0 ) {
abort(); // For sticklers: don't try to access argv[0] if argc <= 0.
}

auto args = span( argv, argc );

// The program takes two command-line arguments: the hostname and "path" part of the URL.
// Print the usage message unless there are these two arguments (plus the program name
// itself, so arg count = 3 in total).
if ( argc != 3 ) {
cerr << "Usage: " << args.front() << " HOST PATH\n";
cerr << "\tExample: " << args.front() << " stanford.edu /class/cs144\n";
return EXIT_FAILURE;
}

// Get the command-line arguments.
const string host { args[1] };
const string path { args[2] };

// Call the student-written function.
get_URL( host, path );
} catch ( const exception& e ) {
cerr << e.what() << "\n";
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}

bytestream.cc

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdexcept>

#include "byte_stream.hh"

using namespace std;

ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity ) {}
static size_t SHUT_RDWR=2;
void Writer::push( string data )
{
if(this->ByteStreamState==SHUT_RDWR){
set_error();
return;
}
uint64_t datasize(data.size());
if(this->headroom_<datasize){
set_error();
datasize=this->headroom_;
data.resize(datasize);
}
this->buffer+=data;
this->buffered+=datasize;
this->pushed+=datasize;
this->headroom_-=datasize;
}

void Writer::close()
{
if(this->ByteStreamState==SHUT_RDWR){
return;
}
this->ByteStreamState=SHUT_RDWR;
}

void Writer::set_error()
{

// unsigned int WriteOFF_0
// unsigned int Overflow_1
// unsigned int ReadOFF_2
this->errorsize++;
// string WriteOFF="Unable to write because write has been turned off";
// string Overflow="Data exceeds the maximum limit";
// string ReadOFF="Unable to read because read has been turned off";
}

bool Writer::is_closed() const
{
if(this->ByteStreamState==SHUT_RDWR){
return true;
}
return false;
}

uint64_t Writer::available_capacity() const
{
return this->headroom_;
}

uint64_t Writer::bytes_pushed() const
{
return this->pushed;
}

string_view Reader::peek() const
{
return std::string_view(this->buffer);
}

bool Reader::is_finished() const
{
if(this->ByteStreamState==SHUT_RDWR&&buffered==0){
return true;
}
return false;
}

bool Reader::has_error() const
{
if(this->errorsize!=0){
return true;
}
return false;
}

void Reader::pop( uint64_t len )
{
if(len<1){
return;
}
else if(len>this->buffered)
{
len=this->buffered;
}
this->buffer.erase(0, len);
this->buffered-=len;
this->headroom_+=len;
this->poped+=len;
}

uint64_t Reader::bytes_buffered() const
{
return this->buffered;
}

uint64_t Reader::bytes_popped() const
{
return this->poped;
}


bytestream.hh

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
#pragma once

#include <queue>
#include <stdexcept>
#include <string>
#include <string_view>

class Reader;
class Writer;

class ByteStream
{
protected:
unsigned int errorsize=0;
uint64_t capacity_;
uint64_t buffered=0;
uint64_t pushed=0;
uint64_t poped=0;
uint64_t headroom_=capacity_;
std::string buffer{};
size_t ByteStreamState=4;
// Please add any additional state to the ByteStream here, and not to the Writer and Reader interfaces.

public:
explicit ByteStream( uint64_t capacity );

// Helper functions (provided) to access the ByteStream's Reader and Writer interfaces
Reader& reader();
const Reader& reader() const;
Writer& writer();
const Writer& writer() const;
};

class Writer : public ByteStream
{
public:
void push( std::string data ); // Push data to stream, but only as much as available capacity allows.

void close(); // Signal that the stream has reached its ending. Nothing more will be written.
void set_error(); // Signal that the stream suffered an error.

bool is_closed() const; // Has the stream been closed?
uint64_t available_capacity() const; // How many bytes can be pushed to the stream right now?
uint64_t bytes_pushed() const; // Total number of bytes cumulatively pushed to the stream
};

class Reader : public ByteStream
{
public:
std::string_view peek() const; // Peek at the next bytes in the buffer
void pop( uint64_t len ); // Remove `len` bytes from the buffer

bool is_finished() const; // Is the stream finished (closed and fully popped)?
bool has_error() const; // Has the stream had an error?

uint64_t bytes_buffered() const; // Number of bytes currently buffered (pushed and not popped)
uint64_t bytes_popped() const; // Total number of bytes cumulatively popped from stream
};

/*
* read: A (provided) helper function thats peeks and pops up to `len` bytes
* from a ByteStream Reader into a string;
*/
void read( Reader& reader, uint64_t len, std::string& out );