这里给出CS 144 Lab 0: networking warmup的翻译。

实验资料:

实验0:网络热身

欢迎来到CS144:计算机网络入门。在这个热身实验中,你将在你的计算机上安装Linux,学习如何在互联网上手动执行一些任务,用C++编写一个小程序,在互联网上获取一个网页,并实现(在内存中)网络的一个关键抽象概念:写入器和读取器之间的可靠字节流。我们预计这个热身赛将花费你2到6个小时来完成(未来的实验将花费你更多的时间)。在进入实验之前,最好先阅读整个实验的内容。

0. 合作政策

编程作业必须是你自己的工作:你必须编写你为编程作业提交的所有代码,但我们作为作业的一部分提供给你的代码除外。请不要复制和粘贴来自StackOverflow,GitHub或其他来源的代码。如果你的代码基于你在网上或其他地方找到的样例,请在提交的源代码中的注释中引用URL。

与他人合作:你不能把你的代码给别人看,也不能看别人的代码,更不能看往年的解决方案。你可以与其他学生讨论作业,但不要抄袭任何人的代码。如果你与其他学生讨论作业,请在你提交的源代码中的注释中列出他们的姓名。请参阅课程管理讲义了解更多详细信息,如果有任何不清楚的地方,请在Piazza上询问。

Piazza:请随时在Piazza上提问,但请不要发布任何源代码。

1. 在你的电脑上设置GNU/Linux

CS144的作业要求使用GNU/Linux操作系统和一个支持C++ 2017标准的最新C++编译器。请在这三个选项中选择一个:

  1. 推荐:安装CS144的VirtualBox虚拟机映像(说明见https://stanford.edu/class/cs144/vm_howto/vm-howto-image.html)。

  2. 运行Ubuntu 18.04 LTS版本,然后运行我们的安装脚本。你可以在你的实际电脑上,在VirtualBox内,或者在EC2或其他虚拟机上进行(详细指南见https://stanford.edu/class/cs144/vm_howto/vm-howto-iso.html)。

  3. 使用另一个GNU/Linux发行版,但要注意你可能会在此过程中遇到障碍,并且需要对它们进行调试。你的代码将在Ubuntu 18.04 LTS和g++ 8.2.0上进行测试,必须在这些条件下正常编译和运行。提示在https://stanford.edu/class/cs144/vm_howto/vm-howto-byo.html

2. 手动联网

让我们开始使用网络。你将手动完成两项任务:检索网页(就像一个网络浏览器)和发送电子邮件(像一个电子邮件客户端)。这两项任务都依赖于一种叫做可靠的双向无序字节流的网络抽象:你将在终端机上输入一串字节,并且最终以相同的顺序将相同的字节序列传递给另一台计算机(服务器)上运行的程序。服务器用它自己的字节序列进行响应,并传递回你的终端。

2.1 获取一个网页

  1. 在一个网络浏览器中,访问http://cs144.keithw.org/hello,并观察结果。

  2. 现在,你将用手动做与浏览器相同的事情。

    • (a) 在你的虚拟机上,运行telnet cs144.keithw.org http。这告诉telnet程序在你的计算机和另一台计算机(名为cs144.keithw.org)之间打开一个可靠的字节流,并在该计算机上运行一个特定的服务:“http”服务,即万维网使用的超文本传输协议。(计算机的名称有一个数字对应(104.196.238.229,一个IPv4地址)。服务的名称也是如此(80,一个TCP端口号)。我们稍后会进一步讨论这些问题。)

      如果你的计算机已经被正确设置并在互联网上,你会看到:

      user@computer:~$ telnet cs144.keithw.org http
      Trying 104.196.238.229...
      Connected to cs144.keithw.org.
      Escape character is '^]'.

      如果你需要退出,按住ctrl并按下],然后输入close

    • (b) 输入GET /hello HTTP/1.1。这将告诉服务器URL的路径部分。(从第三个斜线开始的部分。)

    • (c) 输入Host: cs144.keithw.org。这将告诉服务器URL的主机部分。(http://和第三个斜线之间的部分。)

    • (d) 再按一次回车键 。这告诉服务器,你已经完成了你的HTTP请求。

    • (e) 如果一切顺利,你会看到与你的浏览器所看到的相同的响应,前面有HTTP报头信息,告诉浏览器如何解释该响应。

  3. 作业。现在你知道如何手动获取一个网页了,向我们展示你的能力吧! 使用上述技术来获取URL http://cs144.keithw.org/lab0/sunetid,用你自己的主要SUNet ID来代替sunetid。你会在X-Your-Code-Is: 报头收到一个秘密代码。保存你的SUNet ID 和代码以包含在你的文章中。

2.2 给自己发送电子邮件

现在你知道了如何获取网页,现在是时候发送电子邮件了,再次使用可靠的字节流向另一台计算机上运行的服务发送。

  1. 在斯坦福的网络中,运行telnet smtp-unencrypted.stanford.edu smtp
    (如果你不在斯坦福的网络中,请先登录到cardinal.stanford.edu rst,然后运行这些命令)。“smtp”服务指的是简单邮件传输协议,用于发送电子邮件信息。如果一切顺利,你会看到:

    user@computer:~$ telnet smtp-unencrypted.stanford.edu smtp
    Trying 171.64.13.18...
    Connected to smtp-unencrypted.stanford.edu.
    Escape character is '^]'.
    220 smtp-unencrypted.stanford.edu ESMTP Postfix
  2. 第一步:确定你的计算机与电子邮件服务器的关系。输入
    HELO mycomputer.stanford.edu。等待看到“250 smtp-unencrypted.stanford.edu”。

  3. 下一步:谁在发送电子邮件?输入MAIL FROM: sunetid@stanford.edu
    用你的SUNet ID替换sunetid。(是的,有可能给出一个假的“from”地址。电子信件有点像邮政部门的真实邮件,因为回信地址的准确性(大部分)取决于信誉系统。在这一点上,回信地址的准确性(大部分)取决于信誉誉系统。请不要滥用这一点!)如果一切顺利,你会看到“250 2.1.0 Ok”。

  4. 下一步:谁是收件人?输入RCPT TO: sunetid @stanford.edu。用你的SUNet ID替换sunetid。如果一切顺利,你会看到“250 2.1.5 OK”。

  5. 现在是上传电子邮件信息本身的时候了。输入DATA,告诉服务器你准备好开始了。如果一切顺利,你将看到 “354 End data with <CR><LF>.<CR><LF>”。

  6. 现在你正在给自己发送一封邮件。首先,开始输入你将在电子邮件客户端中看到的标题。在标题的末尾留一个空行。

    354 End data with <CR><LF>.<CR><LF>
    From: sunetid@stanford.edu
    To: sunetid@stanford.edu
    Subject: Hello from CS144 Lab 0!
    
  7. 输入电子邮件的正文——任何你喜欢的内容。完成后,在一行中用一个点结束:.。期待看到类似的内容:“250 2.0.0 Ok: queued”。

  8. 输入QUIT,结束与电子邮件服务器的对话。检查你的收件箱和垃圾邮件文件夹,确保你收到了邮件。

  9. 作业。现在你知道如何用手发送电子邮件,向我们展示你的能力。使用上述技术,从你自己那里发送一封邮件,cs144grader@gmail.com。

2.3 监听和连接

你已经看到了可以用telnet做什么:一个客户端程序,它可以向外连接到其他计算机上运行的程序。现在是时候尝试做一个简单的服务器了:那种等待客户连接的程序。

  1. 在一个终端窗口中,在你的虚拟机上运行netcat -v -l -p 9090。你应该看到:

    user@computer:~$ netcat -v -l -p 9090
    Listening on [0.0.0.0] (family 0, port 9090)
  2. netcat运行。在另一个终端窗口,运行telnet localhost 9090(也在你的虚拟机上)。

  3. 如果一切顺利,netcat将打印出类似于“Connection from localhost 53500 received!” 的内容。

  4. 现在试着在netcat(服务器)或telnet(客户端)的任何一个终端窗口中输入信息。
    注意,你在一个窗口中输入的任何东西都会出现在另一个窗口中,反之亦然。你必须要输入回车,才能对字节行传输。

  5. netcat窗口中,通过输入ctrl-C退出该程序。注意,telnet程序也立即退出了。

3. 使用操作系统流套接字编写网络程序

在这个热身实验的下一部分,你将编写一个简短的程序,通过互联网获取一个网页。你将利用Linux内核和其他大多数操作系统提供的一个功能:在两个程序之间创建一个可靠的双向无序字节流的能力,一个在你的计算机上运行,另一个在互联网上的另一台计算机上运行(例如,Apache或nginx等Web服务器,或netcat程序)。

这种功能被称为流套接字。对于你的程序和Web服务器来说,该套接字看起来就像一个普通的(文件)描述符(类似于磁盘上的描述符,或者类似于stdinstdout I/O流)。当两个流套接字连接在一起时,任何写入一个套接字的字节最终都会以同样的顺序从另一台计算机上的另一个套接字出现。

然而,在现实中,互联网并不提供可靠的字节流的服务。相反,互联网真正做的唯一事情是尽最大努力将短的数据片(称为互联网数据报)送到它们的目的地。每个数据报都包含一些元数据(报头),如源地址和目的地址(来自哪台计算机,目标是哪台计算机),以及一些将被传送到目的计算机的有效载荷数据(最多约1,500字节)。

尽管网络试图传递每一个数据报,但在实践中,数据报可能(1)丢失,(2)不按顺序传递,(3)传递时内容被改变,甚至(4)重复传递不止一次。通常情况下,连接两端的操作系统的工作是将”尽力而为的数据报”(互联网提供的抽象概念)变成”可靠的字节流”(应用程序通常想要的抽象概念)。

两台计算机必须合作,以确保流中的每个字节最终都能在其适当的位置上被传递到另一侧的流套接字。他们还必须告诉对方,他们准备从另一台计算机接受多少数据,并确保不发送超过对方愿意接收的数据。所有这些都是通过一个商定的方案完成的,该方案是在1981年制定的,称为传输控制协议,或TCP。

在本实验中,你将简单地使用操作系统对传输控制协议的已有支持。你将编写一个名为”webget “的程序,创建一个TCP流套接字,连接到一个Web服务器,并获取一个页面,这与你在本实验中早先所做的工作很相似。在未来的实验中,你将实现这个抽象概念的另一面,通过自己实现传输控制协议,从不那么可靠的数据报中创建一个可靠的字节流。

3.1 让我们开始吧——获取和构建启动代码

  1. 实验作业将使用一个名为”Sponge”的启动代码库。在你的虚拟机上,运行git clone https://github.com/cs144/sponge来获取实验的源代码。

  2. 可选的:请随意将你的仓库备份到一个私有的GitHub/GitLab/Bitbucket仓库(例如,使用https://stackoverflow.com/questions/10065526/github-how-to-make-a-fork-of-public-repository-private的说明),但请绝对确保你的工作保持私有。

  3. 进入实验0目录:cd sponge
  4. 创建一个目录来编译实验软件:mkdir build
  5. 进入build目录:cd build
  6. 设置build系统:cmake ..
  7. 编译源代码:make(你可以运行make -j4来使用四个处理器)。
  8. 在build目录外,打开并开始编辑writeups/lab0.md。这是你的实验报告的模板,将包括在你提交的报告中。

3.2 现代C++:大部分是安全的,但仍然是快速和低级的

实验作业将以现代C++风格完成,使用最近(2011年)的功能来尽可能安全地编程。这可能与过去要求你写C++的方式不同。关于这种风格的参考,请参见《C++核心指南》(http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)。

其基本思想是确保每个对象都被设计成具有尽可能小的公共接口,有大量的内部安全检查,很难被不当使用,并且知道如何清理自己。我们希望避免”配对”操作(例如malloc/free,或new/delete),在这种情况下,配对的后半部分有可能不发生(例如,如果一个函数提前返回或抛出一个异常)。相反,操作发生在对象的构造函数中,而相反的操作发生在析构函数中。这种风格被称为”资源获取即初始化”,或RAII。

特别地,我们希望你能:

  • 使用语言文档(https://en.cppreference.com)作为资源。
  • 永远不要使用malloc()free()
  • 永远不要使用newdelete
  • 基本上不使用原始指针(*),只在必要时使用”智能”指针(unique_ptrshared_ptr)(CS144中不需要)。
  • 避免使用模板、线程、锁和虚函数(CS144中不需要)。
  • 避免使用C风格的字符串(char *str)或字符串函数(strlen(), strcpy())。这些都很容易出错,使用std::string代替。
  • 不要使用C语言风格的转换(例如,(FILE *)x)。如果有必要,请使用C++ static_cast(在CS144中一般不需要)。
  • 倾向于通过const引用传递函数参数(例如:const Address & address)。
  • 使每个变量成为const,除非它需要被改变。
  • 使每个方法成为const,除非它需要改变对象。
  • 避免使用全局变量,给每个变量以尽可能小的范围。
  • 在提交作业之前,请运行make格式来规范编码风格。

关于使用Git:实验以Git(版本控制)仓库的形式发布——这是一种记录修改、检查版本以帮助调试和跟踪源代码来源的方式。请在工作中经常进行小规模的提交,并使用提交信息来确定哪些地方发生了变化以及为什么会发生变化。柏拉图式的理想情形是,每一次提交都应该进行编译,并且应该稳定地朝着越来越多的测试通过方向发展。做出小的提交有助于调试(如果每次提交都能编译,并且提交信息能清楚地描述该提交所做的事情,那么调试就会容易得多),并且通过记录你在一段时间内的稳定进展来防止你被指控作弊——这是一项有用的技能,对包括软件开发在内的任何职业都有帮助。评卷人将会阅读你的提交信息,以了解你是如何开发实验的解决方案的。如果你还没有学会如何使用Git,请在CS144的办公时间内寻求帮助或查阅教程(如https://guides.github.com/introduction/git-handbook)。最后,我们欢迎你将你的代码存储在GitHub、GitLab、Bitbucket等的私人仓库中,但请确保你的代码不被公众访问。

3.3 阅读Sponge文档

为了支持这种编程风格,Sponge的类将操作系统函数(可以从C语言中调用)封装在现代的C++中。

  1. 使用一个网络浏览器,在https://cs144.github.io/doc/lab0,阅读一下启动代码的文档。
  2. 特别注意FileDescriptorSocketTCPSocketAddress类的文档。(注意SocketFileDescriptor的一种类型,而TCPSocketSocket的一种类型)。
  3. 现在,阅读libsponge/util目录下描述这些类的接口的头文件:file_descriptor.hhsocket.hh,和address.hh

3.4 编写webget

现在是实现webget的时候了,这是一个利用操作系统的TCP支持和流套接字抽象在互联网上获取网页的程序——就像你在本实验室早些时候手动完成的那样。

  1. build目录中,用文本编辑器或IDE打开../apps/webget.cc

  2. get_URL函数中,删去以”// Your code here“为开头的注释。

  3. 按照本章所述实现简单的Web客户端,使用你之前使用的HTTP(Web)请求的格式。使用TCPSocketAddress类。

  4. 提示:

    • 请注意,在HTTP中,每一行都必须以”\r\n”结束(只使用”\n”或endl是不够的)。
    • 当你完成了向套接字写请求时,通过结束你的出站字节流(从你的套接字到服务器的套接字的字节流)告诉服务器你已经完成了请求。你可以通过调用一个参数为SHUT_WRTCPSocketshutdown方法来做到这一点。作为回应,服务器将向你发送一个回复,然后将结束它自己的出站字节流(从服务器的套接字到你的套接字的字节流)。你会发现传入字节流已经结束了,因为当你读完来自服务器的整个字节流后,你的套接字将到达”EOF”(结束)。(如果你不关闭你的出站字节流,服务器将等待一段时间,让你发送更多的请求,并且也不会结束它的出站字节流)。
    • 确保从服务器上读取并打印所有的输出,直到套接字到达”EOF”(结束)——单次调用read是不够的。
    • 我们预计你需要写十行左右的代码。
  5. 通过运行make来编译你的程序。如果你看到一个错误信息,你需要在继续之前将其删除。

  6. 通过运行./apps/webget cs144.keithw.org /hello来测试你的程序。这与你在网络浏览器中访问http://cs144.keithw.org/hello时看到的情况相比,有什么不同?它与第2.1节的结果相比如何?请用你喜欢的任何http URL进行实验或测试吧!

  7. 当它看起来工作正常时,运行make check webget来运行自动测试。在实现get_URL功能之前,你应该能看到以下情况:

    1/1 Test #25: lab0_webget ......................***Failed 0.00 sec
    Function called: get_URL(cs144.keithw.org, /hasher/xyzzy).
    Warning: get_URL() has not been implemented yet.
    ERROR: webget returned output that did not match the test's expectations

    完成作业后,你会看到:

    4/4 Test #4: lab0_webget ...................... Passed 0.14 sec
    100% tests passed, 0 tests failed out of 4
  8. 评分程序会用不同的主机名和路径来运行你的webget程序,而不是用make check来运行,所以要确保它不会只用make check使用的主机名和路径。

4. 内存中可靠的字节流

到现在为止,你已经看到了可靠的无序字节流的抽象是如何在互联网上进行通信的,尽管互联网本身只提供”不可靠的”数据报的服务。

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

你的字节流也会受到控制:它被初始化时有一个特定的容量:它愿意在自己的内存中存储的最大字节数。字节流将限制写者可以写多长的字符串,以确保字节流不会超过其存储容量。当读取器读取字节并将其从流中耗尽时,写入器被允许写入更多。

你的字节流是在单线程中使用的——你不必担心并发的写者/读者、锁定或竞争条件。

下面是writer程序的接口:

// Write a string of bytes into the stream. Write as many
// as will fit, and return the number of bytes written.
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();

这是reader的接口:

// Peek at next "len" bytes of the stream
std::string peek_output(const size_t len) const;

// Remove "len" 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
std::string read(const size_t len);

bool input_ended() const; // `true` if the stream input has ended
bool eof() const; // `true` if the output has reached the ending
bool error() const; // `true` if the stream has suffered an error
size_t buffer_size() const; // the maximum amount that can currently be peeked/read
bool buffer_empty() const; // `true` if the buffer is empty
size_t bytes_written() const; // Total number of bytes written
size_t bytes_read() const; // Total number of bytes popped

请打开libsponge/byte_stream.hhlibsponge/byte_stream.cc,并实现一个提供此接口的对象。当你开发你的字节流实现时,你可以用make check lab0运行自动测试。

接下来是什么?在接下来的四个星期里,你将实现一个系统来提供同样的接口,但不再是在内存中,而是通过一个不可靠的网络,即传输控制协议。

5. 提交

  1. 在你的提交中,请只对webget.cclibsponge顶层的源代码(byte_stream.hhbyte_stream.cc)进行修改。请不要修改libsponge/util中的任何测试或帮助程序。
  2. 在提交任何作业之前,请按顺序运行这些:
    • (a) make format(使编码风格正常化)
    • (b) make(确保代码可以编译)
    • (c) make check_lab0(确保自动测试通过)
  3. 完成编辑writeups/lab0.md,写上这项作业花了你多少时间,以及任何其他评论。
  4. 准备提交时,请按照https://cs144.github.io/submit。在提交之前,请确保你已经提交了你想要的一切。
  5. 如有任何问题,请在周二晚上的实验课上尽快告知课程负责人,或在Piazza上发表问题。祝您好运,欢迎来到CS144!