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)

分别执行以下三行请求:

  1. 告诉服务器 URL 的路径

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

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

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

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

Assignment

相当于课后练习

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

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

返回了一个 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 通讯 Demoarrow-up-right,这个实验属于是更底层的实现了。

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

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

netcat -v -l -p 9090

  1. 新建终端,启动客户端并连接本地服务器

telnet localhost 9090

  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,初始化构建环境

  • 编译代码cmake --build build

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

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

最后还是要更新 g++,参考 Blogarrow-up-right,最后成功

更新 g++ 后成功编译

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/arrow-up-right,而不是 cplusplus.comarrow-up-right(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/helloarrow-up-right 有什么不一样?和你在 Section 2.1 有什么区别?

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

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

实现代码:来自 kimi

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

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

问题解决了!

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

输出正常

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

  • minnow -> 有 apps 和 build

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

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

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

发现和 Section 2 的差不多。

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

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

完整的 webget.cc 的代码

最终成功通过测试...

任务完成

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

4. An in-memory reliable byte stream

实现字节流。

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

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

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

4.1. Writer

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 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 像是一个谜语人

-- 广大网友

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

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

byte_stream.cc

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

  1. 生命周期:

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

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

  1. 线程安全:

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

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

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

byte_stream.hh

cmake测试结果如下

测试通过

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