Lab0 首先有两个小实验热身,然后就是配置开发环境,熟悉实验环境,并实现
ByteStream
据了解,CS144 的 Lab 现在有两个大版本,一个是 Sponge 版本,一个是 Minnow,Sponge 是旧版本,也是大多数人完成实验的版本,8 个 Project 带你实现整个 TCP/IP 协议栈。而 Minnow 是 2023 年春季新上线的版本,新版本据说难度降低了,砍掉了原来最难的 TCP Connection 部分,内容也有所改动。为了更好的实验体验和学习资料,这里我还是选择经典 Sponge 版本来完成实验。
但一件遗憾的事是,新版本 Minnow 上线后旧版本 Sponge 的仓库被官方下线了,我们无法再直接获取到官方的 Sponge 最新仓库代码,只能从别人实验的 GitHub 仓库中回退来获取到原始实验代码,为此后面也踩了点坑和折腾了一下(因为 Git 技术太菜了)。
我的 CS144 代码仓库
Fetch a Web page
使用 telnet 发送 HTTP 报文获取网页内容
一个坑点是步骤中没有说清楚成功要点:输入的速度一定要快
换句话说,只能复制粘贴下面的内容,而不是手动输入
GET /hello HTTP/1.1
Host: cs144.keithw.org
Connection: close
第二个热身 Send yourself an email 我没有做,因为文档上的步骤需要 Stanford 的邮箱
Listening and connecting
netcat 作为 server,telnet 作为 client,相互通信
配置开发环境
实验环境需要 cmake, git 和最新的 g++
,整个 Lab 采用 Modern C++ 作为开发语言,C++ 17
标准。(当然官网现在的 mirrow 版本的 Lab 已经是 C++ 20
标准了)
竟然遇到了下面这个错误
在 buffer.hh
中添加 #include <stdexcept>
就可以解决了(更新:后来换了一个貌似更新的实验仓库,直接就能编译通过了)
编译安装 doxygen
Webget
要求实现 get_URL
函数,功能为向指定 IP 地址发送 HTTP GET 请求,然后输出所有响应。
要求调用 Linux kernel 自带的 TCP/IP 协议栈实现即可,Sponge 已经将 C-Style 的 Socket 封装成了几个 C++ 类:FileDescriptor
, Socket
, TCPSocket
, and Address
classes。具体见官方文档。
按照 HTTP 报文格式模拟发送 HTTP 报文即可实现
代码如下:
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).
TCPSocket sock;
sock.connect(Address(host, "http"));
sock.write("GET " + path + " HTTP/1.1\r\n");
sock.write("Host: " + host + "\r\n");
sock.write("Connection: close\r\n");
sock.write("\r\n");
sock.shutdown(SHUT_WR);
while (!sock.eof()) {
cout << sock.read();
}
sock.close();
cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
// cerr << "Warning: get_URL() has not been implemented yet.\n";
}
测试结果,如下:
ByteStream
要求实现一个运行在内存的有序可靠字节流(An in-memory reliable byte stream),类似于管道。要求如下:
- 字节流可以从写入端写入,并以相同的顺序,从读取端读取
- 字节流是有限的,写者可以终止写入。而读者可以在读取到字节流末尾时,产生
EOF
标志,不再读取 - 所实现的字节流必须支持流量控制,以控制内存的使用。当所使用的缓冲区爆满时,超过缓冲区容量的内容直接丢弃。字节流会返回成功写入的字节数
- 写入的字节流可能会很长,必须考虑到字节流大于缓冲区大小的情况。即便缓冲区只有1字节大小,所实现的程序也必须支持正常的写入读取操作
在单线程环境下执行,因此不用考虑各类条件竞争问题。
网上很多博客是采用 std::deque<char>
实现的,用它实现很好想也容易写,但是 std::deque<char>
的底层实现并不是连续的数组,会带来额外的开销,而现在的需求是容量(capacity)是在初始化后就确定的,因此我采用 std::vector<char>
来实现。
为了能够放入 capacity
bytes,我们需要开辟 capacity + 1
的空间。否则就无法区分全空和全满状态。
声明部分代码如下:
//! \brief An in-order byte stream.
//! Bytes are written on the "input" side and read from the "output"
//! side. The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
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::vector<char> _buffer; //!< The buffer of the stream
size_t _capacity; //!< The capacity of the stream
size_t _written_cnt; //!< The number of bytes written
size_t _read_cnt; //!< The number of bytes read
int _head, _tail; //!< The head and tail of the buffer
bool _input_ended_flag = false; //!< Flag indicating that the input has ended
bool _error = false; //!< 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;
//!@}
};
成员函数实现代码如下:
ByteStream::ByteStream(const size_t capacity) : _buffer(capacity + 1), _capacity(capacity), _written_cnt(0),
_read_cnt(0), _head(0), _tail(_capacity) {}
size_t ByteStream::write(const string &data) {
auto ret = min(data.size(), remaining_capacity());
for (size_t i = 0; i < ret; ++i) {
_tail = (_tail + 1) % (_capacity + 1);
_buffer[_tail] = data[i];
}
_written_cnt += ret;
return ret;
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
string ret;
auto n = min(len, buffer_size());
for (size_t i = 0; i < n; ++i) {
ret.push_back(_buffer[(_head + i) % (_capacity + 1)]);
}
return ret;
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
auto n = min(len, buffer_size());
_head = (_head + n) % (_capacity + 1);
_read_cnt += n;
}
//! 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
string ByteStream::read(const size_t len) {
auto ret = peek_output(len);
pop_output(len);
return ret;
}
void ByteStream::end_input() { _input_ended_flag = true; }
bool ByteStream::input_ended() const { return _input_ended_flag; }
size_t ByteStream::buffer_size() const { return (_tail - _head + 1 + _capacity + 1) % (_capacity + 1) ; }
bool ByteStream::buffer_empty() const { return buffer_size() == 0; }
bool ByteStream::eof() const { return _input_ended_flag && buffer_empty(); }
size_t ByteStream::bytes_written() const { return _written_cnt; }
size_t ByteStream::bytes_read() const { return _read_cnt; }
size_t ByteStream::remaining_capacity() const { return _capacity - buffer_size(); }
hey 哥们我发现 github 上官方换了 minnow 后我的环境就死活搭不起来。你现在用的 lab 的 pdf 是哪个版本的呀~ 最新的版本的环境配不动。
PDF 我用的 Fall 2021 的版本,实验框架还是旧的 Sponge,新的 Minnow 网上教程几乎没有,当然写经典旧版学习更方便🙂
你好~ 请问 Fall 2021 的版本请问还有嘛?我今天下午搭了一个下午 2023 Spring 的环境都 fail 了,人麻了 TAT。可以一起交流下嘛,mail: pkuwkl@gmail.com (☆ω☆),如果可以的话我们在上面交流吧~
可以呀,加 qq 525687841 聊吧
老哥,搭环境搭麻了,方便加你qq请教一下吗
可以的
老哥 拉的你的仓库 回退到你的仓库 写好lab0的webget make check_webget 然后 testing webget 直接就跳出来Build target check_webget 没有看到样例测试 拉了好几遍都是这样 用的ubuntu20.04 这是为什么啊 /(ㄒoㄒ)/
先 make 编译再 make check_webget 吧,webget 就只有 1 个测试用例吧
对啊 就是这样的 但是 tests pass 跟时间那些都没跳出来 然后就直接build target了 只跳出来两行 一行testing webget 然后下一行就是Build target check_webget了 = =
没看懂,我也没遇到过🌝
老哥,你跑webget测试之前进行的那个tr命令,删除r是啥意思呢?我本来也死活check不了,看了你的截图尝试了一下才好的
就是删除文件里的 ‘\r’,把 CRLF 转换为 LF(因为 Linux 下一般是 LF,不转换的话脚本好像没法运行)
老哥,telnet热身里面那三行请求体的第三行connection写成conncetion了
噢噢,还真是(其实请求体我也是从其它博客文章复制过来的🙃)
fixed ✅
请问为什么不能在 read 函数里更新 _read_cnt?
这样写就会出错,只有在 pop 函数里跟新_read_cnt 才能过全部testcase
这可能是因为多线程的影响吗?
错误testcase
建议仔细阅读实验文档的要求,文档里说了整个实验都是单线程,不需要考虑多线程。
_read_cnt
文档里也有说明,是 total number of bytes popped。感谢博主解答!是我文档没有读清楚,现在明白了~
想请教一下输完GET /hello HTTP/1.1
Host: cs144.keithw.org
Conncetion: close之后直接跳出Connection closed by foreign host如何解决
尽量快的把接下来的几行命令输进去
你好,我在make的时候,没有出现buffer.cc中的错误,但是出现了address.cc中的错误。
网上说这个错误是因为没包含头文件导致的,但这个有#include “address.hh”
可以麻烦看看吗
The errors you’re encountering in your C++ code are related to the usage of the
std::array
class template without specifying the required template parameters, and a conversion issue when trying to return astd::pair
.std::array
: Thestd::array
needs two template arguments: the type of the elements and the size of the array. You’re not providing any, which is why the compiler complains about an initializer but incomplete type. You should define yourip
andport
variables with proper types and sizes, likestd::array<char, SIZE>
whereSIZE
is the number of elements you expect in the arrays.{, }
to the required typestd::pair<std::string, unsigned short>
. This might be due to the variablesip
andport
not being used correctly. Ensureip.data()
returns a string or can be converted to a string andstoi(port.data())
successfully converts theport
data to an integer.Here’s a corrected snippet considering these points, but you’ll need to adjust the array types and sizes according to your actual data:
Ensure you’ve included the array header (
#include <array>
) and adjust the array sizes (16
and6
in the example) as per your requirements. The conversion tostd::string
and then tounsigned short
should handle the types correctly for thestd::pair
being returned.我觉得这种简单的编译问题问 GPT 比问我来得快和简单😂