type
Post
status
Published
slug
2023/06/12/CS144-writeup-Lab-Checkpoint-0:-networking-warmup
summary
CS144 writeup - Lab Checkpoint 0: networking warmup
tags
开发
Linux
C++
CS144
category
学习思考
icon
password
new update day
Property
Oct 22, 2023 01:31 PM
created days
Last edited time
Oct 22, 2023 01:31 PM
0 简介
这里记录了笔者配置 CS144 计算机网络实验环境的一些步骤。
CS144 Lab0 实验指导书
1 环境搭建
- 运行 Ubuntu 22.10 版本,安装下面所需要的软件包
sudo apt update && sudo apt install git cmake gdb build-essential clang \ clang-tidy clang-format gcc-doc pkg-config glibc-doc tcpdump tshark
- 你可以使用另一个 GNU/Linux 发行版,但是请注意,在此过程中你可能会遇到障碍,你需要熟练的自己调试去解决它们。你的代码将在带有
g++ 12.2
的 Ubuntu 22.10 LTS 上进行测试,并且必须在这些条件下被正确地编译和运行。
2 Networking by hand(手工联网)
2.1 Fetch a Web page(获取一个网页)
- 在浏览器中打开网址:http://cs144.keithw.org/hello 并观察结果。
- 现在,您将手动执行与浏览器相同的操作。
- 在您的 VM 上,运行
telnet
cs144.keithw.org
http
。 - 输入
GET /hello HTTP/1.1
。这告诉服务器 URL 的路径部分。(以第三个斜杠开头的部分。) - 输入
Host:
cs144.keithw.org
。这告诉服务器 URL 的主机部分。 (http:// 和第三个斜杠之间的部分。) - 输入
Connection: close
这告诉服务器您已完成请求,它应该在完成回复后立即关闭连接。 - 再按一次 Enter 键: 。这将发送一个空行并告诉服务器您已完成 HTTP 请求。
- 如果一切顺利,您将看到与您的浏览器看到的相同的响应,前面是告诉浏览器如何解释响应的 HTTP 标头。
运行结果
2.2 Send yourself an email(给自己发送一个邮件)
现在您知道了如何获取网页,是时候发送电子邮件了,再次使用可靠的字节流到另一台计算机上运行的服务。因为现在的邮箱基本上都有身份认证,所以没有再进行演示。
3 使用 OS Socket 流编写一个网络程序
在本热身实验的下一部分,您将编写一个简短的程序,通过 Internet 获取网页。您将利用 Linux 内核和大多数其他操作系统提供的一项功能:在两个程序之间创建可靠的双向字节流的能力,一个在您的计算机上运行,另一个在 Internet 上的另一台计算机上运行(例如,Apache 或 nginx 或 netcat 程序等 Web 服务器)。
此功能称为流套接字。对于您的程序和 Web 服务器,套接字看起来像一个普通的文件描述符(类似于磁盘上的文件,或者类似于 stdin 或 stdout I/O 流)。当连接两个流套接字时,写入一个套接字的任何字节最终将以相同的顺序从另一台计算机上的另一个套接字输出。
然而,实际上,Internet 并不提供可靠的字节流服务。相反,互联网真正做的唯一事情就是“尽最大努力”将短数据(称为互联网数据报)传送到目的地。每个数据报都包含一些元数据(标头),这些元数据(标头)指定了源地址和目标地址等内容——它来自哪台计算机,它正前往哪台计算机——以及一些要传递到目的地的有效负载数据(最多约 1,500 字节)电脑。
尽管网络尝试传送每个数据报,但实际上数据报可能会 (1) 丢失,(2) 传送时乱序,(3) 传送时更改了内容,甚至 (4) 复制并传送不止一次。通常,连接两端操作系统的工作是将“尽力而为的数据报”(Internet 提供的抽象)转换为“可靠的字节流”(应用程序通常需要的抽象)。
两台计算机必须合作以确保流中的每个字节最终都在其适当的位置上被传送到另一端的流套接字。他们还必须告诉对方他们准备从另一台计算机接受多少数据,并确保不要发送超过对方愿意接受的数据量。所有这一切都是使用 1981 年制定的商定方案完成的,称为传输控制协议,或 TCP。
在本实验中,您将简单地使用操作系统预先存在的对传输控制协议的支持。您将编写一个名为“webget”的程序来创建一个 TCP 流套接字、连接到一个 Web 服务器并获取一个页面——就像您之前在本实验中所做的那样。在以后的实验中,您将通过自己实现传输控制协议来实现这种抽象的另一面,从而从不那么可靠的数据报中创建可靠的字节流。
3.1 让我们开始吧——获取并构建起始代码
- 实验室作业将使用名为“Minnow”的入门代码库。在您的 VM 上,运行
git clone
https://github.com/cs144/minnow
以获取实验室的源代码。
- 可选:随意将您的存储库备份到私有 GitHub/GitLab/Bitbucket 存储库(例如,使用
https://stackoverflow.com/questions/10065526/github-how-to-make-a-fork-of-public-repository-private
),但请绝对确保您的工作保持私有。
- 进入Lab 0目录:
cd minnow
- 创建一个目录来编译实验室软件:
cmake -S . -B build
- 编译源代码:
cmake --build build
- 在构建目录之外,打开并开始编辑
writeups/check0.md
文件。这是您的实验室检查点报告模板,将包含在您的提交中。
3.2 代码风格
实验室作业将以现代 C++ 风格完成,该风格使用最近(2011 年)的功能尽可能安全地进行编程。这可能与过去要求您编写 C++ 的方式不同。有关此样式的参考,请参阅 C++ 核心指南 (http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)。
基本思想是确保每个对象都被设计成具有尽可能小的公共接口,有很多内部安全检查并且很难被不当使用,并且知道如何自行清理。我们希望避免“成对”操作(例如 malloc/free 或 new/delete),因为对的后半部分可能不会发生(例如,在函数提前返回或抛出异常的情况下)。正确的方案是,将操作放在对象的构造函数中,而相反的操作发生在析构函数中。这种风格称为“资源获取即初始化”或 RAII。
特别是,我们希望您:
- 使用 https://en.cppreference.com 上的语言文档作为资源。 (我们建议您避免使用 cplusplus.com,它更有可能已过时。)
- 切勿使用 malloc() 或 free()。
- 切勿使用 new 或 delete 。
- 基本上从不使用原始指针 (*),仅在必要时才使用“智能”指针(唯一指针或共享指针)。 (您不需要在 CS144 中使用这些。)
- 避免使用模板、线程、锁和虚函数。 (您不需要在 CS144 中使用这些。)
- 避免使用 C 风格的字符串 (char *str) 或字符串函数(strlen()、strcpy())。这些很容易出错。请改用 std::string。
- 永远不要使用 C 风格的转换(例如,(FILE *)x)。如果必须,请使用 C++ 静态转换(在 CS144 中通常不需要它)。
- 更喜欢通过 const 引用传递函数参数(例如:const Address & address)。
- 使每个变量 const 除非它需要被改变。
- 使每个方法都成为常量,除非它需要改变对象。
- 避免全局变量,并为每个变量提供尽可能小的范围。
- 在提交作业之前,运行
cmake --build build --target tidy
以获得有关如何改进与 C++ 编程实践相关的代码的建议,并运行cmake --build build --target format
以一致地格式化代码。
关于使用Git:这些实验室是作为Git(版本控制)存储库分发的——这是一种记录更改、检查版本以帮助调试和跟踪源代码来源的方式。请在工作时经常进行小的提交,并使用提交消息来确定更改的内容和原因。柏拉图式的理想是,每次提交都应该编译,并且应该稳步地朝着越来越多的测试通过的方向前进。进行小的“语义”提交有助于调试(如果每个提交都编译,并且消息描述了提交所做的一件事,那么调试就容易得多),并且通过记录您随时间的稳定进展来保护您免受作弊的指控,这是一项有用的技能,对包括软件开发在内的任何职业都有帮助。评分员将阅读你的提交信息,以了解你是如何为实验开发解决方案的。如果你还没有学会如何使用Git,请务必在CS144办公时间寻求帮助或咨询教程(例如, https://guides.github.com/introduction/git-handbook)。最后,欢迎您将代码存储在GitHub, GitLab, Bitbucket等私有存储库中,但请确保您的代码不可公开访问。
3.3 阅读 Minnow 支持代码
为了支持这种编程风格,Minnow的类在“现代”C++中包装了操作系统函数(可以从C中调用)。我们已经为您提供了C++概念的包装器我们希望您熟悉CS110/111,特别是套接字和文件描述符。
请仔细阅读公共接口(文件
util/socket.hh
和 util/file_descriptor.hh
中 "public:"
后面的部分)(请注意,Socket是FileDescriptor的一种类型,TCPSocket是Socket的一种类型。3.4 编写 webget
现在是实现webget的时候了,这是一个使用操作系统的TCP支持和流套接字抽象在Internet上获取Web页面的程序,就像你在本实验前面手工做的那样。
- 从构建目录中,在文本编辑器或IDE中打开该文件:
./apps/webget.cc
。
- 在get_URL函数中,找到以“// Your code here。
- 使用前面使用的 HTTP (Web) 请求的格式,实现本文件中描述的简单Web客户机。使用 TCPSocket 和 Address 类。
- 提示:
- 请注意,在HTTP中,每一行都必须以“\r\n”结尾(仅使用“\n”或“Endl”是不够的)。
- 不要忘记在客户的请求中包含
“Connection:Close”
行。告诉服务器,它不应该等待您的客户在此之后发送任何请求。相反,服务器将发送一个回复,然后立即结束其传出的字节流(从服务器的套接字到您的套接字的字节流)。您将发现您的传入字节流已经结束,因为当您读取来自服务器的整个字节流时,您的套接字将到达“EOF”(文件结束)。这就是您的客户端如何知道服务器已经完成了它的回复。 - 确保读取并打印来自服务器的所有输出,直到套接字到达“EOF”(文件结束)--一个单独的调用是不够的。
- 我们希望您需要编写大约十行代码。
- 通过运行make编译程序。如果您看到错误消息,则需要在继续之前把它修好。
- 通过运行
./app/webget
cs144.keithw.org/hello
来测试你的程序。这与您在Web浏览器中访问http://cs144.keithw.org/hello
时所看到的相比如何?如何与2.1节的结果进行比较?请随意尝试--用任何你喜欢的http URL来测试它!
- 当它看起来工作正常时,使用
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
- 评分员将使用比
make check webget
运行的更多不同的主机名和路径来运行webget程序,所以确保它不仅仅适用于单元测试使用的主机名和路径。
4 LAB0实现:内存中的可靠字节流
到目前为止,您已经看到了一个可靠字节流的抽象在跨Internet的COM通信中是如何有用的,尽管Internet本身只提供了“尽最大努力”(不可靠)数据报的服务。
为了完成本周的实验,您将在一台计算机上的内存中实现一个提供此抽象的对象。(你可能在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. void set_error(); // Signal that the stream suffered an error. 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
请打开
src/byte_stream.hh
和 src/byte.stream.cc
文件,并实现一个提供该接口的对象。在开发字节流实现时,您可以使用 cmake --build build --target check0
运行自动化测试。如果所有测试都通过,那么 check0 测试将运行您的实现的速度基准测试 任何超过 0.1 Gbit/s(换句话说,每秒 1 亿位)的速度对于此类目的都是可接受的。 (实现的执行速度可能超过 10 Gbit/s,但这取决于您计算机的速度,这不是必需的。)
对于任何最新问题,请查看课程网站上的实验室常见问题解答,或在实验室课程(或 EdStem 上)中询问您的同学或教职员工。
4.1 src/byte_stream.hh
src/byte_stream.hh
#pragma once #include <memory> #include <queue> #include <stdexcept> #include <string> #include <string_view> class Reader; class Writer; class ByteStream { protected: uint64_t capacity_; // Please add any additional state to the ByteStream here, and not to the Writer and Reader interfaces. uint64_t bytes_buffered_; uint64_t bytes_pushed_; uint64_t bytes_popped_; std::string buffer_; bool is_closed_; bool has_error_; 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; }; 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. void set_error(); // Signal that the stream suffered an error. 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)? 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 }; /* * read: A (provided) helper function thats peeks and pops up to `len` bytes * from a ByteStream Reader into a string; */ void read( Reader& reader, uint64_t len, std::string& out );
4.2 src/byte.stream.cc
src/byte.stream.cc
#include <stdexcept> #include "byte_stream.hh" using namespace std; ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity ) , bytes_buffered_( 0 ) , bytes_pushed_( 0 ) , bytes_popped_( 0 ) , buffer_( "" ) , is_closed_( false ) , has_error_( false ) { // Your code here. } void Writer::push( string data ) { uint64_t data_size = data.size(); if ( data_size > this->available_capacity() ) { data_size = this->available_capacity(); } this->buffer_.append( data.substr( 0, data_size ) ); this->bytes_buffered_ += data_size; this->bytes_pushed_ += data_size; } void Writer::close() { // Your code here. this->is_closed_ = true; } void Writer::set_error() { // Your code here. this->has_error_ = true; } bool Writer::is_closed() const { // Your code here. return this->is_closed_; } uint64_t Writer::available_capacity() const { // Your code here. return this->capacity_ - this->bytes_buffered_; } uint64_t Writer::bytes_pushed() const { // Your code here. return this->bytes_pushed_; } string_view Reader::peek() const { return this->buffer_; } bool Reader::is_finished() const { // Your code here. return this->is_closed_ && this->bytes_buffered_ == 0; } bool Reader::has_error() const { // Your code here. return this->has_error_; } void Reader::pop( uint64_t len ) { // Your code here. if ( len == 0 ) { return; } this->buffer_.erase( 0, len ); this->bytes_buffered_ -= len; this->bytes_popped_ += len; } uint64_t Reader::bytes_buffered() const { // Your code here. return this->bytes_buffered_; } uint64_t Reader::bytes_popped() const { // Your code here. return this->bytes_popped_; }
4.3 运行 check 查看结果
运行 cmake --build build --target check0
得到以下运行结果
Test project /home/tcy/cs144/minnow/build Start 1: compile with bug-checkers 1/10 Test #1: compile with bug-checkers ........ Passed 7.64 sec Start 2: t_webget 2/10 Test #2: t_webget ......................... Passed 1.19 sec Start 3: byte_stream_basics 3/10 Test #3: byte_stream_basics ............... Passed 0.02 sec Start 4: byte_stream_capacity 4/10 Test #4: byte_stream_capacity ............. Passed 0.02 sec Start 5: byte_stream_one_write 5/10 Test #5: byte_stream_one_write ............ Passed 0.02 sec Start 6: byte_stream_two_writes 6/10 Test #6: byte_stream_two_writes ........... Passed 0.02 sec Start 7: byte_stream_many_writes 7/10 Test #7: byte_stream_many_writes .......... Passed 0.07 sec Start 8: byte_stream_stress_test 8/10 Test #8: byte_stream_stress_test .......... Passed 0.02 sec Start 9: compile with optimization 9/10 Test #9: compile with optimization ........ Passed 3.76 sec Start 10: byte_stream_speed_test ByteStream throughput: 2.02 Gbit/s 10/10 Test #10: byte_stream_speed_test ........... Passed 0.26 sec 100% tests passed, 0 tests failed out of 10 Total Test time (real) = 13.03 sec Built target check0
欢迎加入“喵星计算机技术研究院”,原创技术文章第一时间推送。
- 作者:tangcuyu
- 链接:https://expoli.tech/articles/2023/06/12/CS144-writeup-Lab-Checkpoint-0%3A-networking-warmup
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章