Lab0 networking warmup
0 前言
- 在完成实验前,你最好阅读完所有的文档
- 在接下来的八个实验中,你将要实现互联网中几个重要的部分:路由器、网络接口,TCP协议
- 在接下来的四周里,你将实现TCP去提供不同主机之间的可靠字节流。
- 这里的实验不是唯一的,当你发现实验过程中有歧义时,也可以有自己的实现。
1 获取一个网页
1.1 在浏览器中输入 http://cs144.keithw.org/hello并查看结果
Hello, CS144!
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 Connection: close
-
得到如下的结果
HTTP/1.1 200 OK Date: Thu, 16 Mar 2023 01:43:40 GMT Server: Apache Last-Modified: Thu, 13 Dec 2018 15:45:29 GMT ETag: "e-57ce93446cb64" Accept-Ranges: bytes Content-Length: 14 Connection: close Content-Type: text/plain Hello, CS144! Connection closed by foreign host.
1.3 获取自己的SUNID
-
在终端中输入如下字符
telnet cs144.keithw.org http GET /lab0/1021493854 HTTP/1.1 Host: cs144.keithw.org Connection: close
-
得到的结果如下
HTTP/1.1 200 OK Date: Thu, 16 Mar 2023 01:54:27 GMT Server: Apache X-You-Said-Your-SunetID-Was: 1021493854 X-Your-Code-Is: 516681 Content-length: 114 Vary: Accept-Encoding Connection: close Content-Type: text/plain Hello! You told us that your SUNet ID was "1021493854". Please see the HTTP headers (above) for your secret code. Connection closed by foreign host.
2 给自己发邮件
2.1 在终端输入telnet 148.163.153.234 smtp观察结果
Trying 148.163.153.234...
Connected to 148.163.153.234.
Escape character is '^]'.
220 mx0b-00000d03.pphosted.com ESMTP mfa-m020657
2.2 向邮件信息服务器确认自己的主机信息
-
输入HELO mycomputer.stanford.edu
-
返回结果
250 mx0b-00000d03.pphosted.com Hello [39.144.153.12], pleased to meet you
2.3 确认发送者
MAIL FROM: 1021493854@stanford.edu
250 2.1.0 Sender ok
2.4 确认接收者(不在内网无法操作)
RCPT TO: 1021493854@stanford.edu
550 5.1.1 User Unknown
2.5 发送数据
DATA /r/n
From: sunetid@stanford.edu
To: sunetid@stanford.edu
Subject: Hello from CS144 Lab 0!
2.6 接收结果
250 2.0.0 33h24dpdsr-1
Message accepted for delivery”.
3 监听与连接
3.1 在一个终端上,开启监听
netcat -v -l -p 9090
3.2 在另一终端上,连接监听
telnet localhost 9090
3.3 监听终端返回结果
Listening on localhost 9090
Connection received on localhost 56488
3.4 至此两者可以相互通信发送消息
4 用操作系统socket接口写网络程序
4.1 背景知识
-
在这一部分你将用socket接口获取网页
-
为了让你的程序和Web通信,socket就像普通的文件描述符
-
当两个socket相连接,一端的数据将会按顺序发送到令一端。
-
但网络层并不能提供一个可靠的数据流,主要有以下四种情况
- 丢失报文
- 报文失序
- 报文内容错误
- 报文重复
-
因此两台电脑间必须协作让数据流中的每一个字符正确到达,这个机制在1981年被实现,被叫做传输控制协议(TCP)
-
在本次lab中,你将会用操作系统提供支持TCP的接口来实现一个webget,连接上服务器并获取web页面
4.2 准备操作
-
下载
git clone https://github.com/cs144/sponge
-
构建
cd sponge mkdir build cd build cmake .. make
-
阅读sponge文件夹
apps # 应用层函数 CMakeLists.txt # 构建脚本 doctests # 内置函数测试脚本 libsponge # 应用层函数 tests # lab测试脚本 build # 构建文件 compile_commands.json # 编译命令 etc # 配置文件 README.md # 使用文档 writeups # lab文档
-
阅读FileDescriptor,Socket,TCPSocket,Address类
-
阅读file descriptor.hh, socket.hh, and address.hh头文件
4.3 具体实现
-
创建一个tcp_socket
-
向tcp_socket写入GET请求
-
向读取tcp_socket回复,直到收到eof标志
void get_URL(const string &host, const string &path) { TCPSocket sock; sock.connect(Address(host, "http")); string url = "GET " + path + " " + "HTTP/1.1" + "\r\n"; url += "Host: " + host + "\r\n"; url += "Connection: close\r\n"; url += "\r\n"; sock.write(url); constexpr size_t BUFFER_SIZE = 1024; string buf; while (!sock.eof()) { sock.read(buf, BUFFER_SIZE); cout << buf; } }
5 实现一个基于内存的缓冲区
5.1 实现
- 至此我们已经可以看到如何在可靠字节流的基础上进行不同进程之间的通信
- 在这个实验中,我们将实现基于单机的内存可靠字节流,主要有以下功能:
- 字节在input端写入,在output端读出
- 字节流是有限的,写端可以停止写入,读端读到字节流的尾端,将收到EOF标志
- 你的字节流将具有流量控制的功能,用capacity表示内存存储的最大字节数
- 你的实现将会被用在单一线程,你不必考虑并发问题
- capacity用于限制内存大小,无法限制写者写入的zui’da
5.2 几个需要注意的点
- input_ended表示停止输入,当停止输入并且缓冲区为空时,则置eof为true,表示请求结束。
- 每当pop_output时将数据累计到byte_written中
- 先写入数据的先读出,并且具有获取开头数据的功能,因此可用std::deque来进行处理。
5.3 结果
- ByteStream.h
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.
bool _error{}; //!< Flag indicating that the stream suffered an error.
size_t _byte_written = 0;
size_t _byte_read = 0;
size_t _capacity{};
std::deque<char> q{};
bool _eof{};
bool _input_eof{};
- ByteStream.cpp
ByteStream::ByteStream(const size_t capacity): _capacity(capacity) {}
size_t ByteStream::write(const string &data) {
// 最多能写入多少
int len_write = min(data.size(), remaining_capacity());
_byte_written += len_write;
for (int i = 0; i < len_write; i++)
q.push_back(data[i]);
return len_write;
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
string str = "";
int len_peek = min(len, buffer_size());
for (int i = 0; i < len_peek; i++)
str.push_back(q[i]);
return str;
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
int len_pop = min(len, buffer_size());
for (int i = 0; i < len_pop; i++)
q.pop_front();
_byte_read += len_pop;
if (buffer_empty() && input_ended())
_eof = true;
}
//! 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 str = peek_output(len);
pop_output(len);
return str;
}
void ByteStream::end_input() {
if (buffer_empty())
_eof = true;
_input_eof = true;
}
bool ByteStream::input_ended() const {
return _input_eof;
}
size_t ByteStream::buffer_size() const {
return q.size();
}
bool ByteStream::buffer_empty() const {
return q.empty();
}
bool ByteStream::eof() const {
return _eof;
}
size_t ByteStream::bytes_written() const {
return _byte_written;
}
size_t ByteStream::bytes_read() const {
return _byte_read;
}
size_t ByteStream::remaining_capacity() const {
return _capacity - q.size();
}