CS144计算机网络lab0: networking warmup

实验网址:https://cs144.github.io/

1. 搭建GUN/Linux环境

这里就不详细展开了,需要的小伙伴可以看这两篇博客:
Linux系统安装
GUN环境安装

2. 手动上网

2.1 访问网页

  1. 使用浏览器,访问网页 http://cs144.keithw.org/hello
  2. 使用telnet访问
    • 输入如下指令
    telnet cs144.keithw.org http
    GET /hello HTTP/1.1
    Host: cs144.keithw.org
    Conncetion: close
    • 观察结果如下:

      PS:实验成功的关键是输入的速度足够快,可以复制粘贴输入,速度慢了会返回404错误。这里telnet首先使用http端口连接服务器,然后提交一个GET请求,获得服务器 /hello下的内容,然后关闭连接。这实际上与我们用浏览器访问该网址是一样的,只不过这里不是图形化页面,同时会返回所有的信息。
  3. 任务:访问网址:http://cs144.keithw.org/lab0/sunetid ,其中sunetid可以自行设置,可以获得一个密码在 X-Your-Code-Is。
    • 命令
    telnet cs144.keithw.org http
    GET /lab0/peng HTTP/1.1
    Host: cs144.keithw.org
    Conncetion: close
    • 运行结果就不展示了,这里注意还是输入的速度要快,和我一样打字速度慢的可以先写好,复制粘贴。

2.2 送给你自己一封邮件

这个实验,使用CS144提供的实验不能够完成,因为使用到Stanford的邮箱服务,因此这里使用了QQ邮箱来完成该实验。

  • 在使用QQ邮箱进行实验之前,首先需要获得自己QQ邮箱的16位校验码,使用的是校验码登录。设置步骤如下:

    • 网页登录qq邮箱
    • 点击设置,账户设置
    • 在账户安全下面有 POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务选项,如下图所示:
    • 先点击开启SMTP服务,开启后点击生成授权码,生成一个十六位的校验码,记录下来。
  • 得到16位校验码之后,我们还不能使用,因为登录时使用的是校验码和qq号的64位base加密码。

    • 打开64base加密网址:https://base64.us/
    • 输入你的qq号点击加密,把结果记录下来
    • 输入你的16位校验码点击加密,把结果记录下来
      笔者进行了如下记录:
  • 接下来就可以进行实验啦

    • 使用telnet连接qq邮箱的服务器
    • 给服务器发送一个helo请求,helo可以大写,也可以小写
    • 收到回复之后发送auth login,请求登录
    • 接下来发送qq号的64位base码
    • 发送邮箱的16位校验码的base码
    • 登录成功之后就可以发送邮件啦
    • 发送 MAIL FROM: <QQ号@qq.com> 告诉邮箱服务器邮件发送方,就你目前登录的qq邮箱
    • 发送 RCPT TO: <手机号@163.com> 告诉邮箱服务器邮件接收方,就你想要发送的目标邮箱,这里笔者使用了自己的163邮箱
    • 发送 DATA +回车 告诉服务器要输入邮件信息了
    • 发送信息的模板如下:
    From: <QQ号@qq.com> #发送方
    To: <手机号@163.com> #接收方
    subject: test # 邮件主题,好多样例里只发送主题, 注意发送邮件内容需要在主题后有一空行
    # 空行
    I am the ... # 邮件内容,邮件内容后也需要一空行
    # 空行
    . # ‘.’ 加回车提示邮件内容发送完成
    • 发送完邮件之前,可以输入QUIT加回车关闭telnet连接
  • 实验完整的命令如下:

telnet smtp.qq.com smtp
HELO smtp.qq.com
auth login
QQ号的64位base编码:自己的qq号base64编码
16位校验码的base编码:16位校验码的base64编码
MAIL FROM: <qq号@qq.com>
RCPT TO: <手机号@163.com>
DATA
From: <qq号@qq.com>
To: <手机号@163.com>
subject: test
I am the ...
.
  • 实验结果

    其中箭头指向的是来自服务器的回复信息。

2.3 听和连接

这个实验是实现运行在同一台电脑上的两个终端之间进行通信。

  • 实验步骤:
    • 打开一个终端,输入命令 netcat -v -l -p 9090,开启该终端的监听服务
    • 打开另一个终端, 使用 telnet 进行连接, 输入指令 telnet localhost 9090
    • 如果运行顺利的话,两个终端都会收到消息,客户端(telnet)提示连接成功,服务端(netcat)提示,收到客户端(telnet)的连接
    • 你可以在服务端或者客户端输入信息,按下回车键后会同步显示在另一个终端上
    • 最后可以在服务端(netcat)输入ctrl-C关闭服务
  • 运行结果

3. 编写一个使用系统流套接字的网络程序

这里使用linux系统的套接字实现一个小程序webget,即我们之前使用telnet进行访问网页的程序。为了编写这个程序,我们首先需要使用git将github代码仓库里的代码克隆到本地,这里笔者使用的Vscode的Git管理,构建VscodeGit管理的教程,可以参考这篇博客:https://www.cnblogs.com/Fight-go/p/15851321.html

3.1 克隆项目

为了克隆这个实验项目,在终端输入如下指令:

  • git clone https://github.com/cs144/sponge 就可以将源码拷贝到本地了。
  • cd sponge 进入实验0目录
  • mkdir build创建build目录
  • cd build 进入编译目录
  • cmake .. 设置编译系统
  • make 编译源码,(可以使用make -j4 使用四个处理器)

3.2 编码风格建议

在课程建议使用C++11的代码风格,这里笔者可能只使用了一部分,做后面的实验中应该也会优化之前的代码。
这里是课程给出的编码建议:

  • Use the language documentation at https://en.cppreference.com as a resource.
  • Never use malloc() or free().
  • Never use new or delete.
  • Essentially never use raw pointers (*), and use “smart” pointers (unique ptr or shared ptr) only when necessary. (You will not need to use these in CS144.)
  • Avoid templates, threads, locks, and virtual functions. (You will not need to use these in CS144.)
  • Avoid C-style strings (char *str) or string functions (strlen(), strcpy()). These are pretty error-prone. Use a std::string instead.
  • Never use C-style casts (e.g., (FILE *)x). Use a C++ static cast if you have to (you generally will not need this in CS144).
  • Prefer passing function arguments by const reference (e.g.: const Address & address).
  • Make every variable const unless it needs to be mutated.
  • Make every method const unless it needs to mutate the object.
  • Avoid global variables, and give every variable the smallest scope possible.
  • Before handing in an assignment, please run make format to normalize the coding style.

3.3 读Sponge文件

关键需要阅读 libsponge/util目录下的文件:file descriptor.hh, socket.hh, and address.hh.

3.4 编写webget程序

  • 使用IDE或者文本编辑器打开 apps/webget.cc 文件
  • 在get_URL()函数中,编写代码,可以看到开始提示"// Your code here"

套接字本身是一个文件描述符,程序通过它对“某个文件(字节流抽象)”执行操作,这里通过套接字的EOF标志符判断是否到达文件流结尾,如果到达,则停止读取,否则,一直进行读取。
代码如下:

void get_URL(const string &host, const string &path) {
// Your code here.
// You will need to connect to the "http" service on
// the computer whose name is in the "host" string,
// then request the URL path given in the "path" string.
TCPSocket local_socket;
Address sever_addr = Address(host, "http"); // get the address of sever
local_socket.connect(sever_addr); // connect to the sever
// Then you'll need to print out everything the server sends back,
// (not just one call to read() -- everything) until you reach
// the "eof" (end of file).
// send GET request
// string get_request = "GET/ HTTP/1.1\r\nHost: " + host + "\r\n" +
// "Accept: " + path + "\r\n" + "Connection: close\r\n\r\n";
string get_request = "GET " + path + " HTTP/1.1\r\n"
+ "Host: " + host + "\r\n"
+ "Connection: close\r\n\r\n";
local_socket.write(get_request);
// read the message from sever
while (!local_socket.eof()){
cout << local_socket.read();
}
local_socket.close();
if (local_socket.closed()){
// cout << "Connection closed by foreign host." << endl;
return;
}
cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
cerr << "Warning: get_URL() has not been implemented yet.\n";
}
  • 编写完程序,可以运行make,如果看到错误信息,需要首先修复该信息
  • 测试程序,通过运行./apps/webget cs144.keithw.org /hello,可以看到通过网页访问的结果
  • 测试正确后,运行make check_webget来执行自动化测试

4. 一个记忆可靠的字节流

因特网本身不对传输可靠性做保证,而是由终端即服务器和客户端的操作系统来保证可靠性。我们现在要实现的是位于TCP连接两端的套接字中的字节流数据结构。TCPSocket中有两个ByteStream,inbound用于接收数据,outbound用于发送数据。 大体上,ByteStream具有一定的容量,最大允许存储该容量大小的数据;在读取端读出一部分数据后,它会释放掉已经被读出的内容,以腾出空间继续让写端写入数据。
字节流以ByteStream类实现,在文件byte_stream.hh中定义,实现方法在byte_stream.cc中。这里笔者创建了一个字符串类型的buffer变量,来作为缓冲区的存储空间,还添加了一系列成员来实现读和写的工作,这里实现读和写的功能都是使用字符串类型的方法实现的。
代码如下:

#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include <string>
//! \brief An in-order byte stream.
//! Bytes are written on the "input" side and read from the "output"
//! side. The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
class ByteStream {
private:
// Your code here -- add private members as necessary.
size_t _capacity; // capacity of buffer
size_t _bytes_written; // total len of written bytes
size_t _bytes_read; // total len of read bytes
size_t _buffer_size; // the size of buffer
size_t _remaining_capacity; // now the len of buffer
std::string _buffer; // buffer of bytestream
bool _input_ended; // flag indiacating that input is end
bool _eof; // end of file flag
bool _buffer_empty; // flag indicating that the buffer is empty
// Hint: This doesn't need to be a sophisticated data structure at
// all, but if any of your tests are taking longer than a second,
// that's a sign that you probably want to keep exploring
// different approaches.
bool _error{}; //!< Flag indicating that the stream suffered an error.
public:
//! Construct a stream with room for `capacity` bytes.
ByteStream(const size_t capacity);
//! \name "Input" interface for the writer
//!@{
//! Write a string of bytes into the stream. Write as many
//! as will fit, and return how many were written.
//! \returns the number of bytes accepted into the stream
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() { _error = true; }
//!@}
//! \name "Output" interface for the reader
//!@{
//! Peek at next "len" bytes of the stream
//! \returns a string
std::string peek_output(const size_t len) const;
//! Remove 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
//! \returns a string
std::string read(const size_t len);
//! \returns `true` if the stream input has ended
bool input_ended() const;
//! \returns `true` if the stream has suffered an error
bool error() const { return _error; }
//! \returns the maximum amount that can currently be read from the stream
size_t buffer_size() const;
//! \returns `true` if the buffer is empty
bool buffer_empty() const;
//! \returns `true` if the output has reached the ending
bool eof() const;
//!@}
//! \name General accounting
//!@{
//! Total number of bytes written
size_t bytes_written() const;
//! Total number of bytes popped
size_t bytes_read() const;
//!@}
};
#endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include "byte_stream.hh"
// Dummy implementation of a flow-controlled in-memory byte stream.
// For Lab 0, please replace with a real implementation that passes the
// automated checks run by `make check_lab0`.
// You will need to add private members to the class declaration in `byte_stream.hh`
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
ByteStream::ByteStream(const size_t capacity) : _capacity(capacity),
_bytes_written(0), _bytes_read(0), _buffer_size(0), _remaining_capacity(capacity), _buffer('0',_capacity),
_input_ended(false), _eof(false), _buffer_empty(true){
_error = false;
}
size_t ByteStream::write(const string &data) {
if (buffer_empty()){// see if buffer is empty
_buffer_empty = false;
}
if (data.size() >= _remaining_capacity){
size_t len = _remaining_capacity;
_buffer.replace(_buffer.begin() + _buffer_size, _buffer.end(), data);
_bytes_written += _remaining_capacity;
_buffer_size = _capacity;
_remaining_capacity = 0;
return len;
}
_buffer.replace(_buffer.begin() + _buffer_size, _buffer.begin() + _buffer_size + data.size(), data);
_bytes_written += data.size();
_buffer_size += data.size();
_remaining_capacity -= data.size();
return data.size();
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
return _buffer.substr(0, len);
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
_buffer.erase(0, len);
_remaining_capacity += len;
_buffer_size -= len;
_bytes_read += len;
if (_buffer_size == 0){
_buffer_empty = true;
if (input_ended())
_eof = true;
}
}
//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \param[in] len bytes will be popped and returned
//! \returns a string
std::string ByteStream::read(const size_t len) {
std::string read_txt = peek_output(len);
pop_output(len);
return read_txt;
}
void ByteStream::end_input() {
_input_ended = true;
if(buffer_empty())
_eof = true;
}
bool ByteStream::input_ended() const { return _input_ended; }
size_t ByteStream::buffer_size() const { return _buffer_size; }
bool ByteStream::buffer_empty() const { return _buffer_empty; }
bool ByteStream::eof() const { return _eof; }
size_t ByteStream::bytes_written() const { return _bytes_written; }
size_t ByteStream::bytes_read() const { return _bytes_read; }
size_t ByteStream::remaining_capacity() const { return _remaining_capacity; }
  • 编写完成后运行 make check_lab0运行所有的测试样例
posted @   Fight!GO  阅读(637)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示