Lab 0: Networking Warmup

网络热身

1. Docs

预计用时 2 ~ 6 h。

1.1. Main Point

  • 安装 GNU/Linux

  • Write a small program in C++ that fetches a Web page over the Internet

  • Implement (in memory) one of the key abstractions of networking: a reliable stream of bytes between a writer and a reader.

1.2. 先决 TASK 0: Set up GNU/Linux on your computer

Lab 需要使用 GNU/Linux 作为系统和支持 C++2023标准的编译器

  • 这里选择使用 VM 虚拟机 Ubuntu 作为实验环境

  • 配备 GNU

1.3. Test

联网测试:

rain0832@ubuntu:~$ ping -c 4 google.com
PING google.com (198.18.0.94) 56(84) bytes of data.
64 bytes from 198.18.0.94 (198.18.0.94): icmp_seq=1 ttl=128 time=1.62 ms
64 bytes from 198.18.0.94 (198.18.0.94): icmp_seq=2 ttl=128 time=1.57 ms
64 bytes from 198.18.0.94 (198.18.0.94): icmp_seq=3 ttl=128 time=2.62 ms
64 bytes from 198.18.0.94 (198.18.0.94): icmp_seq=4 ttl=128 time=2.45 ms

--- google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3008ms
rtt min/avg/max/mdev = 1.572/2.064/2.622/0.474 ms

测试成功。

C++ 23 标准:

首先是要求 g++ 13,随后可以测试 C++ 23 标准的一些新库是否能够正常引入。

这对于我的老机器确实有点搞。

正常终端指令:

一些 Linux 常用的指令能否正常使用。

2. Networking by hand

Main Topic

  • HTTP 协议:手动发送 HTTP 请求,观察服务器响应

  • SMTP 协议:手动共发送邮件,遵循 SMTP 协议

  • 网络通信:本地的服务器 与 本地客户端的简单数据传输

2.1. Fetch a Web page

告诉 telnet 程序计算机在与 cs144.keithw.org之间打开一个 reliable byte stream。并且需要运行 http 服务 (Hyper-Text Transfer Protocol)

rain0832@ubuntu:~$ telnet cs144.keithw.org http
Trying 198.18.0.99...
Connected to cs144.keithw.org.
Escape character is '^]'.

分别执行以下三行请求:

  1. 告诉服务器 URL 的路径

  2. 告诉服务器 URL 的 host 部分

  3. 告诉服务器已经完成请求

  4. 最后加入空行,告诉服务器已经完成了 HTTP request

GET /hello HTTP/1.1
Host: cs144.keithw.org
Connection: close

最后顺利得到一个请求 就像 http://cs144.keithw.org/hello 上看到的那样

HTTP/1.1 200 OK
Date: Mon, 24 Feb 2025 12:50:09 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.

Assignment

相当于课后练习

SUNet ID 是斯坦福的师生唯一标识

我没有SUNet ID,这里用虚拟的 test 来假装我的 SUNet ID...作为实验来说够用了

rain0832@ubuntu:~$ telnet cs144.keithw.org http
Trying 198.18.0.99...
Connected to cs144.keithw.org.
Escape character is '^]'.
GET /lab0/test HTTP/1.1                                         
Host: cs144.keithw.org
Connection: close

HTTP/1.1 200 OK
Date: Mon, 24 Feb 2025 13:06:10 GMT
Server: Apache
X-You-Said-Your-SunetID-Was: test
X-Your-Code-Is: 590984
Content-length: 108
Vary: Accept-Encoding
Connection: close
Content-Type: text/plain

Hello! You told us that your SUNet ID was "test". Please see the HTTP headers (above) for your secret code.
Connection closed by foreign host.

返回了一个 X-Your-Code-Is 的 header,记录下来

  • X-You-Said-Your-SunetID-Was: test

  • X-Your-Code-Is: 590984

实验成功

2.2. Send yourself an email

无法接上 斯坦福的VPN...GG,

痛在我不是留子。有机会我去那边读研究生的时候在弄这个吧。

Main Topic:

  • telnet连接邮件服务器

  • SMTP 命令 (Simple Mail Transfer Protocol,简单邮件传输协议)

    • HELO mycomputer.stanford.edu标识你的计算机,向服务器发起连接请求

    • MAIL FROM: your_sunet_id@stanford.edu指定发件人地址

    • DATA表示开始邮件内容

    • 邮件内容结束后,输入一个单独的点号(.)并按回车键,表示邮件内容结束

  • 最后,QUIT结束与邮件服务器的对话,检查你的邮箱有没有收到邮件

Assignment:

用上面的技术给身边的人发送邮件。

2.3. Listening and connecting

监听 和 连接。有点类似我之前写的 C 的 TCP 实验差不多。

可以参考复习:TCP 通讯 Demo,这个实验属于是更底层的实现了。

在 Linux 甚至可以直接用netcat和 telnet 就可以建立简单的通讯服务

  1. 新建终端,启动监听 port 9090 的服务器

netcat -v -l -p 9090

rain0832@ubuntu:~$ netcat -v -l -p 9090
Listening on 0.0.0.0 9090
  1. 新建终端,启动客户端并连接本地服务器

telnet localhost 9090

rain0832@ubuntu:~$ telnet localhost 9090
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
  1. 这时候 netcat会提示成功连接

Connection received on localhost 49380

  1. 进行通讯,在双方进行文本输入,回车后在异端都可以显示。实验成功

  2. 退出通讯

    • netcat服务器端按Ctrl+C退出。

    • telnet客户端输入^](按住Ctrl键,然后按]键),然后输入quit退出。

3. Writing a network program using an OS stream socket

3.1. Let’s get started—setting up the repository on your VM and on GitHub

  1. 先克隆官方的仓库。

git clone https://github.com/cs144/minnow

  1. 开一个个人仓库...把 CS144 的 grader 加进 collaborators...这一步我跳过了。毕竟用的虚拟机,代码后面统一转过去吧。

3.2. Compiling the starter code

CMake 编译代码 熟悉代码库的结构

  1. CMake 我还没安装,先 sudo apt 一下

  2. 居然不支持 3.18.xx...要求真高

  • 创建构造目录cmake -S . -B build,初始化构建环境

rain0832@ubuntu:~/CS144/minnow$ cmake -S . -B build
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Setting build type to 'Debug'
-- Building in 'Debug' mode.
-- Configuring done (0.6s)
-- Generating done (0.2s)
-- Build files have been written to: /home/rain0832/CS144/minnow/build
  • 编译代码cmake --build build

初次编译失败,大概是少了一个 库...

最终发现是因为 GNU 的版本太低了,不支持 C++20 的 <format>

最后还是要更新 g++,参考 Blog,最后成功

rain0832@ubuntu:~/CS144/minnow$ cmake --build build
[ 10%] Building CXX object src/CMakeFiles/minnow_debug.dir/byte_stream.cc.o
[ 10%] Building CXX object util/CMakeFiles/util_debug.dir/address.cc.o
[ 15%] Building CXX object src/CMakeFiles/minnow_debug.dir/byte_stream_helpers.cc.o
[ 21%] Linking CXX static library libminnow_debug.a
[ 21%] Built target minnow_debug
[ 26%] Building CXX object tests/CMakeFiles/minnow_testing_debug.dir/common.cc.o
[ 31%] Building CXX object util/CMakeFiles/util_debug.dir/debug.cc.o
In file included from /home/rain0832/CS144/minnow/tests/common.hh:4,
                 from /home/rain0832/CS144/minnow/tests/common.cc:1:
/home/rain0832/CS144/minnow/util/debug.hh:3:10: fatal error: format: No such file or directory
    3 | #include <format>
      |          ^~~~~~~~
compilation terminated.
make[2]: *** [tests/CMakeFiles/minnow_testing_debug.dir/build.make:79: tests/CMakeFiles/minnow_testing_debug.dir/common.cc.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:2978: tests/CMakeFiles/minnow_testing_debug.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs....
[ 36%] Building CXX object util/CMakeFiles/util_debug.dir/eventloop.cc.o
In file included from /home/rain0832/CS144/minnow/util/debug.cc:1:
/home/rain0832/CS144/minnow/util/debug.hh:3:10: fatal error: format: No such file or directory
    3 | #include <format>
      |          ^~~~~~~~
compilation terminated.
make[2]: *** [util/CMakeFiles/util_debug.dir/build.make:93: util/CMakeFiles/util_debug.dir/debug.cc.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [CMakeFiles/Makefile2:2786: util/CMakeFiles/util_debug.dir/all] Error 2
make: *** [Makefile:101: all] Error 2

更新 g++ 后成功编译

rain0832@ubuntu:~/CS144/minnow$ cmake --build build
[ 15%] Built target minnow_debug
[ 21%] Building CXX object util/CMakeFiles/util_debug.dir/debug.cc.o
[ 26%] Building CXX object tests/CMakeFiles/minnow_testing_debug.dir/common.cc.o
[ 31%] Building CXX object util/CMakeFiles/util_debug.dir/eventloop.cc.o
[ 36%] Linking CXX static library libminnow_testing_debug.a
[ 36%] Built target minnow_testing_debug
[ 42%] Building CXX object apps/CMakeFiles/stream_copy.dir/bidirectional_stream_copy.cc.o
[ 47%] Building CXX object util/CMakeFiles/util_debug.dir/file_descriptor.cc.o
[ 52%] Linking CXX static library libstream_copy.a
[ 52%] Built target stream_copy
[ 57%] Building CXX object util/CMakeFiles/util_debug.dir/helpers.cc.o
[ 63%] Building CXX object util/CMakeFiles/util_debug.dir/random.cc.o
[ 68%] Building CXX object util/CMakeFiles/util_debug.dir/socket.cc.o
[ 73%] Linking CXX static library libutil_debug.a
[ 78%] Built target util_debug
[ 84%] Building CXX object apps/CMakeFiles/webget.dir/webget.cc.o
[ 89%] Building CXX object apps/CMakeFiles/tcp_native.dir/tcp_native.cc.o
[ 94%] Linking CXX executable webget
[100%] Linking CXX executable tcp_native
[100%] Built target webget
[100%] Built target tcp_native

3.3. Modern C++: mostly safe but still fast and low-level

本小节介绍 CS144 Lab 的基础是 现代 C++ 风格的代码。介绍现代 C++ 的特效。

we would like you to:

  • 使用语言文档作为 https://en.cppreference.com/,而不是 cplusplus.com(out of date)

  • 不再使用 malloc() or free()

  • 不再使用 new or delete

  • 不再使用 raw pointers (*),而是在必要的时候使用 "smart" pointers

  • 避免 templates, threads, locks, and virtual functions

  • 避免 C 风格的 字符串 和字符串处理函数,这容易出错。而是使用 std : : string

  • 避免 C 风格的 强制类型转换。而是使用 C++ static_cast

  • 避免全局变量。而是给每个变量尽量小的 scope

  • 首选使用 const 引用传递函数参数

  • 首选使用 const 来标注所有变量,除非它需要被改变 mutate

  • 首选使用 const 来标注所有方法,除非他需要改变对象 mutate the object

  • 提交前,运行cmake--build build--target tidy以获取有关如何改进与 C++ 编程实践相关的代码的建议,以及cmake--build build--target format以一致地格式化代码。

To repeat: 尽量频繁的使用 Commit,并且使用 Commit to identify what to change and why

3.4. Reading the Minnow support code

阅读 Minnow 的公共接口。里面提供了一些现代 C++ 的容器。

util/socket.hh

util/file descriptor.hh

请注意,Socket 是 FileDescriptor 的一种类型,而 TCPSocket 是 Socket 的一种类型。

3.5. Writing webget

现在要写一个程序 fetch 一个 Web,通过系统的 TCP 和 stream-socket 抽象(类似上面我说的以前写的 TCP 实验)

  1. 打开 ../apps/webget.cc

  2. get_URL 函数:

    1. 实现简单的 Web 客户端

    2. 使用 HTTP 的 format

    3. 使用 TCPSocket and Address 类

  3. Hints:

    1. HTTP 的换行 ended with "\r\n" (用 "\n" 或者 endl 是不够的)

    2. 不要忘记 Connection: close 在客户端请求,实际上在读到 EOF 的时候会也会停止

    3. 确保 read and print all output from 服务端,直到 socket 达到 EOF

    4. 写大约 10 行代码

  4. 运行cmake--build build,然后自己 debug

  5. 测试./apps/webget cs144.keithw.org /hello,和你直接打开 http://cs144.keithw.org/hello 有什么不一样?和你在 Section 2.1 有什么区别?

  6. 如果一切正常,运行cmake--build build--target check webget,在get_URL还没有完成的时候会有报错和警告,完后之后就可以得到 Test Passed 的信息

本小节主要是需要实现 get_URL 的基本功能

实现代码:来自 kimi

void get_URL(const string &host, const string &path)
{
    // 创建一个 TCP 套接字
    TCPSocket socket;
         
    // 解析主机地址并连接
    Address server_address(host, "http");
    socket.connect(server_address);

    // 构造 HTTP 请求
    string request = "GET " + path + " HTTP/1.1\r\n";
    request += "Host: " + host + "\r\n";
    request += "Connection: close\r\n\r\n";

    // 发送请求
    socket.write(request);

    // 读取响应
    string response;
    while (!socket.eof()) {
        response += socket.read();
    }

    // 打印响应内容
    cout << response << endl;

    // 报错信息,Lab 自带
    cerr << ... << endl;
    cerr << ... << endl;
}

第一版代码固然是有误的,根据 GNU 的提示,是 line 22 的 socket.read() 必须要含有一个参数。所以可以假设将数据读入到 buffer 中,再把 buffer 加入 response

#include "socket.hh"

#include <cstdlib>
#include <iostream>
#include <span>
#include <string>

using namespace std;

void get_URL(const string& host, const string& path) {
    // create TCP socket
    TCPSocket socket;

    // parse the address and connect
    Address server_address(host, "http");
    socket.connect(server_address);

    // create HTTP request
    string request = "GET " + path + " HTTP/1.1\r\n";
    request += "Host: " + host + "\r\n";
    request += "Connection: close\r\n\r\n";

    // send the request
    socket.write(request);

    // read the response
    string response;
    string buffer;  // 用于存储每次读取的数据
    while (!socket.eof()) {
        socket.read(buffer);  // 读取数据到 buffer
        response += buffer;   // 将 buffer 的内容追加到 response
        buffer.clear();       // 清空 buffer 以便下一次读取
    }

    // print the response
    cout << response << endl;
}

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;
}

这次的 cmake 可以通过了,但是这时候发现...文档给的命令有点问题...或者是我的打开方式不对...在 ./apps 在貌似没有 webget 文件夹啊

问题解决了!

执行./apps/webget cs144.keithw.org /hello

输出正常

rain0832@ubuntu:~/CS144/minnow/build$ ./apps/webget cs144.keithw.org /hello
HTTP/1.1 200 OK
Date: Tue, 25 Feb 2025 12:21:33 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!

Function called: get_URL(cs144.keithw.org, /hello)
Warning: get_URL() has not been implemented yet.

原来是因为,目录大概是这样的

  • minnow -> 有 apps 和 build

其中 build 是 cmake 创建的执行文件,而 apps 里面的则是 webget.cc 文件

build里面也包含了一个 apps,这个 apps 里面的则是 webget...

这时候把根目录改成 ../build 再执行官方给的之林那个即可,这时候可以得到上面的结果。

发现和 Section 2 的差不多。

但是有 2 lines 的提示,get_URL() 还没完全实现。貌似两行的 err 是 cerr 引起的,删去之后,重新 cmake 发现好多了。

rain0832@ubuntu:~/CS144/minnow/build$ ./apps/webget cs144.keithw.org /hello
HTTP/1.1 200 OK
Date: Tue, 25 Feb 2025 12:30:59 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!

于是回去更改 get_URL 的代码,更改了一些

完整的 webget.cc 的代码

#include "socket.hh"

#include <cstdlib>
#include <iostream>
#include <span>
#include <string>

using namespace std;

void get_URL( const string& host, const string& path )
{
  // create TCP socket
  TCPSocket socket;

  // parse the address and connect
  Address server_address(host, "http");
  socket.connect(server_address);

  // create HTTP request
  string request = "GET " + path + " HTTP/1.1\r\n";
  request += "Host: " + host + "\r\n";
  request += "Connection: close\r\n\r\n";

  // send the request
  socket.write(request);
  
  // read the response
  string response;
  string buffer;
  while(!socket.eof())
  {
	socket.read(buffer);
  	response += buffer;
	buffer.clear();
  }

  // extract the body part of the HTTP response
  size_t body_start = response.find("\r\n\r\n") + 4;
  if (body_start != string::npos)
  {
	  cout <<response.substr(body_start);
  }
  else
  {
  // print the response
  	cout << response << endl;
  }
}

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;
}

最终成功通过测试...

rain0832@ubuntu:~/CS144/minnow$ cmake --build build --target check_webget
Test project /home/rain0832/CS144/minnow/build
    Start 1: compile with bug-checkers
1/2 Test #1: compile with bug-checkers ........   Passed    0.71 sec
    Start 2: t_webget
2/2 Test #2: t_webget .........................   Passed    2.26 sec

100% tests passed, 0 tests failed out of 2

Total Test time (real) =   2.98 sec
Built target check_webget

任务完成

主要实现的函数如下,虽然没有任何一行代码是我写的...我也不直到为什么要这么写...钻研一下,基本理解代码框架结构。一共 17 行代码,耗时 3 天......效率泪目

void get_URL( const string& host, const string& path )
{
  // create TCP socket 创建 套接字...
  TCPSocket socket;

  // parse the address and connect 解析地址,把网址转为 IP地址 + 80 端口
  Address server_address(host, "http");

  // 连接服务器
  socket.connect(server_address);

  // create HTTP request
  // 对某网址 发起 HTTP 请求
  // GET 关键字 + path 路径 + HTTP 版本
  string request = "GET " + path + " HTTP/1.1\r\n";
  // 请求 主机字段
  request += "Host: " + host + "\r\n";
  // 请求 一旦响应就进行连接关闭。额外的空行表示结束
  request += "Connection: close\r\n\r\n";

  // send the request
  // 写入请求,发送给服务器
  socket.write(request);
  
  // read the response
  string response; // 接收响应
  string buffer; // 先读入缓存
  // eof 表示响应的结束
  while(!socket.eof())
  {
	socket.read(buffer);
  	response += buffer;
	buffer.clear();
  }

  // extract the body part of the HTTP response
  // find 能找到响应头,也是 \r\n\r\n 表示结束
  // 结束之后就是我们要的响应
  size_t body_start = response.find("\r\n\r\n") + 4;

  // 如果 body_start == string::npos 说明没找到 \r\n\r\n 这时候 整个响应就是正文...直接 print
  if (body_start != string::npos)
  {
	  cout <<response.substr(body_start);
  }
  else
  {
  // print the response
  	cout << response << endl;
  }
}

4. An in-memory reliable byte stream

实现字节流。

  • 实现一个内存中的可靠字节流对象,模拟网络中的可靠字节流。

  • 提供写入和读取接口,并实现流量控制,限制内存使用。

  • 通过自动化测试,并确保实现的性能满足要求(速度大于 0.1 Gbit/s)。

4.1. Writer

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

void push( std::string data );

推送数据 stream ,但必须在容量大小之内

void close();

表示这个 stream 已经 end 了,不需要再写入任何东西

bool is_closed() const;

判断 stream 是否 close

uint64_t available_capacity() const;

当前 stream 还有多少 bytes可以 push

uint64_t bytes_pushed() const;

当前 stream 中总共 push 的 bytes

4.2. Reader

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

std::string_view peek() const;

peek buffer 的 下一个 bytes

void pop( uint64_t len );

从缓存区移除 len 大小 bytes

bool is_finished() const;

判断 stream 是否 close 或 full pop

bool has_error() const;

判断 stream 是否有 error

uint64_t bytes_buffered() const;

当前 buffer 有多少个 bytes

uint64_t bytes_popped() const;

当前已经移除的 bytes 数量

4.3. 代码设计

这几天大概自己试着写了一下代码,也一边看课程一遍 Coding 尝试了许多次。自己还是不大能理解其思路,也没有完全理解 Lab 的精髓。

CS 144 的 Lab Docs 像是一个谜语人

-- 广大网友

跟着网上的 开源仓库 敲了一下代码,试图理解思路。

不知道为什么,25年的 buffer_ 也要求要初始化了。

byte_stream.cc

其中一个难点是返回值的 string_view 的 peek()

  1. 生命周期:

  • std::string_view不拥有数据,因此它引用的底层数据(buffer_)必须在std::string_view的生命周期内有效。

  • 如果buffer_被修改或销毁,std::string_view将引用无效数据。

  1. 线程安全:

  • 如果Reader类在多线程环境中使用,需要确保对buffer_的访问是线程安全的。

另外需要注意各种函数之间的引用关系,还有一些私有变量的定义。

总的来说 Lab0 还是很有挑战的,对于我来说,自由度也很大,也可能跟我的代码能力比较弱有关系。

#include "byte_stream.hh"

using namespace std;

ByteStream::ByteStream( uint64_t capacity )
    : capacity_(capacity), buffer_(), bytes_pushed_(0), bytes_popped_(0), closed_flag_(false)
{}

void Writer::push( string data )
{
  // invail operation
  if(is_closed() || has_error())
  {
    return ;
  }

  // data less? or the capacity() 
  const size_t len = min(data.length(), available_capacity());
  buffer_.append(data.substr(0, len));

  bytes_pushed_ += len;
  return ;
}

void Writer::close()
{
  closed_flag_ = true;
  return ;
}

bool Writer::is_closed() const
{
  return {closed_flag_};
}

uint64_t Writer::available_capacity() const
{
  // total capacity - buffer.
  // buffer = push - pop.
  return {capacity_ - (bytes_pushed_ - bytes_popped_)};
}

uint64_t Writer::bytes_pushed() const
{
  return {bytes_pushed_};
}

// challenge1
// offer only-veiw buffer, instead of buffer_copy
string_view Reader::peek() const
{
  // list init string_view object
  // object -> pointer to -> buffer
  return {this->buffer_};
}

void Reader::pop( uint64_t len )
{
  if(bytes_buffered() == 0)
  {
    return ;
  }

  len = min(len, buffer_.length());
  // remove data from buffer_.
  buffer_.erase(0, len);
  bytes_popped_ += len;
  
  return ;
}

bool Reader::is_finished() const
{
  // if close and buffer.clear -> can finish
  return {closed_flag_ && bytes_buffered() == 0};
}

uint64_t Reader::bytes_buffered() const
{
  return {bytes_pushed_ - bytes_popped_};
}

uint64_t Reader::bytes_popped() const
{
  return {bytes_popped_};
}

byte_stream.hh

#pragma once

#include <cstdint>
#include <string>
#include <string_view>

class Reader;
class Writer;

class ByteStream
{
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;

  void set_error() { error_ = true; };       // Signal that the stream suffered an error.
  bool has_error() const { return error_; }; // Has the stream had an error?

protected:
  // Please add any additional state to the ByteStream here, and not to the Writer and Reader interfaces.
  
  uint64_t capacity_;
  std::string buffer_;
  uint64_t bytes_pushed_ {};
  uint64_t bytes_popped_ {};
  bool closed_flag_ {};
  bool error_ {};
};

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.

  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)?
  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 `max_len` bytes
 * from a ByteStream Reader into a string;
 */
void read( Reader& reader, uint64_t max_len, std::string& out );

cmake测试结果如下

rain0832@ubuntu:~/CS144/minnow$ cmake --build build --target check0
Test project /home/rain0832/CS144/minnow/build
      Start  1: compile with bug-checkers
 1/11 Test  #1: compile with bug-checkers ........   Passed   28.10 sec
      Start  2: t_webget
 2/11 Test  #2: t_webget .........................   Passed    4.14 sec
      Start  3: byte_stream_basics
 3/11 Test  #3: byte_stream_basics ...............   Passed    0.04 sec
      Start  4: byte_stream_capacity
 4/11 Test  #4: byte_stream_capacity .............   Passed    0.03 sec
      Start  5: byte_stream_one_write
 5/11 Test  #5: byte_stream_one_write ............   Passed    0.03 sec
      Start  6: byte_stream_two_writes
 6/11 Test  #6: byte_stream_two_writes ...........   Passed    0.02 sec
      Start  7: byte_stream_many_writes
 7/11 Test  #7: byte_stream_many_writes ..........   Passed    0.19 sec
      Start  8: byte_stream_stress_test
 8/11 Test  #8: byte_stream_stress_test ..........   Passed    0.06 sec
      Start 37: no_skip
 9/11 Test #37: no_skip ..........................   Passed    0.02 sec
      Start 38: compile with optimization
10/11 Test #38: compile with optimization ........   Passed   14.14 sec
      Start 39: byte_stream_speed_test
        ByteStream throughput (pop length 4096):  7.68 Gbit/s
        ByteStream throughput (pop length 128):   1.79 Gbit/s
        ByteStream throughput (pop length 32):    0.45 Gbit/s
11/11 Test #39: byte_stream_speed_test ...........   Passed    0.78 sec

100% tests passed, 0 tests failed out of 11

Total Test time (real) =  47.57 sec
Built target check0

测试通过

4.4. 提交代码

最后就是传统的 Git Submit

这里 Docs 介绍了 Cmake 的一些其他方法,除了进行代码编译,还可以进行代码格式调整和检查 C++ 规范。

cmake --build build --target format

  • to normalize the coding style

  • 规范代码格式

cmake --build build --target tidy

  • 完善符合 C++ 规范的代码

Last updated