仓库地址

https://github.com/meowrain/minnow

实验pdf

https://github.com/meowrain/minnow/blob/main/pdf/check0.pdf
https://cs144.github.io/assignments/check0.pdf

项目构建

  1. 实验室作业将使用一个名为“Minnow”的起始代码库。在您的虚拟机上运行
git clone https://github.com/cs144/minnow

以获取实验室的源代码。

  1. 可选步骤:您可以随意将您的代码库备份到私有的GitHub/GitLab/Bitbucket仓库中(例如,使用https://stackoverflow.com/questions/10065526/
    github-how-to-make-a-fork-of-public-repository-private 上的指导),但请务必确保您的工作保持私密。

  2. 进入实验室目录:cd minnow

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

  4. 编译源代码:cmake --build build

  5. 在构建目录之外,打开并开始编辑writeups/check0.md文件。这是您实验室检查点报告的模板,并将包含在您的提交中。

实验1:实现webget.cc

请阅读公共接口(文件util/socket.hh和util/file_descriptor.hh中“public:”之后的部分。(请注意,Socket是FileDescriptor的一种类型,而TCPSocket是Socket的一种类型。)

https://github.com/meowrain/minnow/blob/main/util/socket.hh
https://github.com/meowrain/minnow/blob/main/util/file_descriptor.hh

可以看上面的源码,有中文注释

是时候实现webget程序了,这是一个使用操作系统的TCP支持和流式套接字抽象来通过互联网获取网页的程序——就像你之前在本实验室中手动做的那样。

  1. 从构建目录打开文件../apps/webget.cc到文本编辑器或集成开发环境(IDE)中。

  2. get URL函数中,按照该文件中描述的实现一个简单的Web客户端,使用你之前使用的HTTP(Web)请求的格式。使用TCPSocketAddress类。

  3. 提示:

    • 请注意,在HTTP中,每一行都必须以“\r\n”结束(仅使用“\n”或endl是不够的)。
    • 不要忘记在客户端的请求中包含“Connection: close”这一行。这告诉服务器,在发送完这个请求后,不应该等待你的客户端发送更多请求。相反,服务器将发送一个回复,然后立即结束其传出字节流(从服务器的套接字到你的套接字的那个)。你将通过读取整个来自服务器的字节流后套接字达到“EOF”(文件结束)来发现你的传入字节流已经结束。这就是你的客户端将知道服务器已经完成其回复的方式。
    • 确保读取并打印服务器的所有输出,直到套接字达到“EOF”(文件结束)——单次调用读取是不够的。
    • 我们预计你需要编写大约十行代码。
  4. 通过运行make来编译你的程序。如果看到错误消息,你需要在继续之前修复它。

  5. 通过运行./apps/webget cs144.keithw.org /hello来测试你的程序。这与你在Web浏览器中访问http://cs144.keithw.org/hello时看到的内容如何比较?它与第2.1节的结果如何比较?随意实验——用你喜欢的任何HTTP URL测试它!

  6. 当它看起来工作正常时,运行cmake --build build --target check webget来运行自动化测试。在实现get URL函数之前,你应该期望看到以下内容:

    $ cmake --build build --target check_webget
    Test project /home/cs144/minnow/build
    Start 1: compile with bug-checkers
    1/2 Test #1: compile with bug-checkers ........ Passed 1.02 sec
    Start 2: t_webget
    2/2 Test #2: t_webget .........................***Failed 0.01 sec
    Function called: get_URL(cs144.keithw.org, /nph-hasher/xyzzy)
    Warning: get_URL() has not been implemented yet.
    ERROR: webget returned output that did not match the test's expectations
    

    完成作业后,你将看到:

    $ cmake --build build --target check_webget
    Test project /home/cs144/minnow/build
    Start 1: compile with bug-checkers
    1/2 Test #1: compile with bug-checkers ........ Passed 1.09 sec
    Start 2: t_webget
    2/2 Test #2: t_webget ......................... Passed 0.72 sec
    100% tests passed, 0 tests failed out of 2
    
  7. 评分者将使用与make check webget不同的主机名和路径来运行你的webget程序——因此,请确保它不仅在使用单元测试的主机名和路径时才有效。

void get_URL( const string& host, const string& path )
{
  auto sc = TCPSocket();
  Address addr = Address( host, "http" );
  sc.connect( addr );
  sc.write( "GET " + path + " " + "HTTP/1.1\r\nHost: " + host + "\r\nConnection: close\r\n\r\n" );
  sc.shutdown( SHUT_WR );
  std::string str;
  while ( !sc.eof() ) {
    sc.read( str );
    std::cout << str;
  }
  sc.close();
  // cerr << "Function called: get_URL(" << host << ", " << path << ")\n";
}


实验2:一个内存中可靠的字节流

一个内存中可靠的字节流

到目前为止,您已经看到了可靠字节流的抽象在通过互联网进行通信中的用处,即使互联网本身只提供“尽力而为”的(不可靠的)数据报服务。

为了完成本周的实验,您将在单台计算机上的内存中实现一个对象,提供这种抽象。 (您可能在CS 110/111中做过类似的事情。)字节被写入“输入”端,并且可以从“输出”端以相同的顺序读取。字节流是有限的:写入者可以结束输入,然后就不能再写入更多字节了。当读取器读取到流的末尾时,它将达到“EOF”(文件结束),并且不能再读取更多字节了。

您的字节流还将进行流控制,以限制其在任何给定时间内的内存消耗。对象被初始化为特定的“容量”:它愿意在任何给定时刻存储在自己内存中的最大字节数。字节流将限制写入者在任何给定时刻可以写入的字节数量,以确保流不会超出其存储容量。当读取器读取字节并从流中排出它们时,写入者被允许写入更多。您的字节流供单线程使用—您不必担心并发的写入者/读取者、锁定或竞态条件。

明确一点:字节流是有限的,但在写入者结束输入并完成流之前,它几乎可以是任意长的。您的实现必须能够处理远远超过容量的流。容量限制在任何给定时刻在内存中保持的字节数(已写入但尚未读取),但不限制流的长度。一个只有一个字节容量的对象仍然可以携带一个长度为几千兆字节的流,只要写入者继续每次写入一个字节,并且读取者在允许写入下一个字节之前读取每个字节。

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

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

你需要实现上面的接口

https://github.com/meowrain/minnow/blob/main/src/byte_stream.hh
你在这里可用看到要实现的函数

#include "byte_stream.hh"

using namespace std;

ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity ) {}

bool Writer::is_closed() const
{
  // Your code here.
  return closed_;
}

void Writer::push( string data )
{
  // 如果流已经关闭或者是有错误发生,那么就设置error为true,然后退出程序
  if ( closed_ || error_ ) {
    set_error();
    return;
  }

  if ( data.length() > available_capacity() ) { // 如果插入数据长度大于可用的容量,那么就截断数据
    data = data.substr( 0, available_capacity() ); // 截断数据以适应可用容量,并修改data为截断后的数据
  }
  // 在双端队列中插入数据,使用copy函数可实现拷贝string中的数据到双端队列buffer_中
  // std::back_inserter 是一个插入迭代器,它提供一种通用的方式来将元素插入到容器的末尾。在这个场景下,每当 std::copy
  // 算法尝试通过迭代器写入一个元素时, std::back_inserter 调用双端队列的 push_back 方法,将新元素添加到队列的末尾。
  std::copy( data.begin(), data.end(), std::back_inserter( buffer_ ) );
  // 写入完成后,把写入的字节数加上data的大小
  bytes_written_ += data.size();
}

void Writer::close()
{
  closed_ = true;
  // Your code here.
}

uint64_t Writer::available_capacity() const
{

  // Your code here.
  uint64_t ava_capacity = capacity_ - buffer_.size();
  return ava_capacity;
}

uint64_t Writer::bytes_pushed() const
{
  // Your code here.
  return bytes_written_;
}

bool Reader::is_finished() const
{
  // Your code here.
  bool res = closed_ && buffer_.empty();
  return res;
}

uint64_t Reader::bytes_popped() const
{
  // Your code here.
  return bytes_read_;
}

string_view Reader::peek() const
{
  // Your code here.
  if ( !buffer_.empty() ) {
    return std::string_view( &buffer_.front(), 1 );
  }
  return std::string_view();
}

void Reader::pop( uint64_t len )
{
  // Your code here.
  if ( len > buffer_.size() ) {
    len = buffer_.size();
  }
  buffer_.erase( buffer_.begin(), buffer_.begin() + len );
  bytes_read_ += len;
}

uint64_t Reader::bytes_buffered() const
{
  // Your code here.
  return buffer_.size();
}