CS144 计算机网络 Lab0:Networking Warmup

这篇具有很好参考价值的文章主要介绍了CS144 计算机网络 Lab0:Networking Warmup。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本科期间修读了《计算机网络》课程,但是课上布置的作业比较简单,只是分析了一下 Wireshark 抓包的结构,没有动手实现过协议。所以最近在哔哩大学在线学习了斯坦福大学的 CS144 计算机网课程,这门课搭配了几个 Lab,要求动手实现一个 TCP 协议,而不是简单地调用系统为我们提供好的 Socket。

实验准备

CS144 Fall2019 的课件和实验指导书可以下载自 CS144 镜像网站,代码可以从我的 Github 仓库获取。

本篇博客将会介绍 Lab0 的实验过程,实验环境为 Ubuntu20.04 虚拟机,使用 VSCode 完成代码的编写。

实验过程

Lab0 有两个任务,第一个任务是实现能发送 Get 请求到任意网址的 webget 程序,第二个任务是实现内存内的可靠字节流。

webget

实验指导书中让我们先用 Telnet 程序连接到斯坦福大学的 Web 服务器上,在命令行中输入 telnet cs144.keithw.org http 并回车,不出意外的话会提示已成功连接上服务器。之后手动构造请求报文,包括请求行和请求头,输入两次回车就能得到响应,响应体内容为 Hello, CS144

CS144 计算机网络 Lab0:Networking Warmup

应用层的 Http 协议使用 TCP 传输层协议进行数据的可靠性传输,由于我们目前还没有实现 TCP 协议,只能先借用一下操作系统写好的的 socket 来发送 http 请求。CS144 的老师们十分贴心地对 socket 库进行了二次封装,类图如下所示:

CS144 计算机网络 Lab0:Networking Warmup

FileDescriptor 的部分代码如下,可以看到内部类 FDWrapper 持有文件描述符,会在析构的时候调用 close() 函数释放对文件描述符的引用 。FileDescriptor 还提供了 read()write() 函数进行文件读写操作:

class FileDescriptor {
    //! \brief A handle on a kernel file descriptor.
    //! \details FileDescriptor objects contain a std::shared_ptr to a FDWrapper.
    class FDWrapper {
      public:
        int _fd;                    //!< The file descriptor number returned by the kernel
        bool _eof = false;          //!< Flag indicating whether FDWrapper::_fd is at EOF
        bool _closed = false;       //!< Flag indicating whether FDWrapper::_fd has been closed

        //! Construct from a file descriptor number returned by the kernel
        explicit FDWrapper(const int fd);
        //! Closes the file descriptor upon destruction
        ~FDWrapper();
        //! Calls [close(2)](\ref man2::close) on FDWrapper::_fd
        void close();
    };

    //! A reference-counted handle to a shared FDWrapper
    std::shared_ptr<FDWrapper> _internal_fd;

  public:
    //! Construct from a file descriptor number returned by the kernel
    explicit FileDescriptor(const int fd);

    //! Free the std::shared_ptr; the FDWrapper destructor calls close() when the refcount goes to zero.
    ~FileDescriptor() = default;

    //! Read up to `limit` bytes
    std::string read(const size_t limit = std::numeric_limits<size_t>::max());

    //! Read up to `limit` bytes into `str` (caller can allocate storage)
    void read(std::string &str, const size_t limit = std::numeric_limits<size_t>::max());

    //! Write a string, possibly blocking until all is written
    size_t write(const char *str, const bool write_all = true) { return write(BufferViewList(str), write_all); }

    //! Write a string, possibly blocking until all is written
    size_t write(const std::string &str, const bool write_all = true) { return write(BufferViewList(str), write_all); }

    //! Close the underlying file descriptor
    void close() { _internal_fd->close(); }

    int fd_num() const { return _internal_fd->_fd; }        //!< \brief underlying descriptor number
    bool eof() const { return _internal_fd->_eof; }         //!< \brief EOF flag state
    bool closed() const { return _internal_fd->_closed; }   //!< \brief closed flag state
};

// 析构的时候自动释放文件描述符
FileDescriptor::FDWrapper::~FDWrapper() {
    try {
        if (_closed) {
            return;
        }
        close();
    } catch (const exception &e) {
        // don't throw an exception from the destructor
        std::cerr << "Exception destructing FDWrapper: " << e.what() << std::endl;
    }
}

我们知道,在 Linux 系统中 “万物皆文件”,socket 也被认为是一种文件,socket 被表示成文件描述符,调用 socket() 函数返回就是一个文件描述符,对 socket 的读写就和文件的读写一样。所以 Socket 类继承自 FileDescriptor 类,同时拥有三个子类 TCPSocketUDPSocketLocalStreamSocket,我们将使用 TCPSocket 完成第一个任务。

第一个任务需要补全 apps/webget.ccget_URL() 函数,这个函数接受两个参数:主机名 host 和请求路径 path

void get_URL(const string &host, const string &path) {
    TCPSocket socket;
    
    // 连接到 Web 服务器
    socket.connect(Address(host, "http"));
    
    // 创建请求报文
    socket.write("GET " + path + " HTTP/1.1\r\n");
    socket.write("Host: " + host + "\r\n\r\n");
    
    // 结束写操作
    socket.shutdown(SHUT_WR);

    // 读取响应报文
    while (!socket.eof()) {
        cout << socket.read();
    }

    // 关闭 socket
    socket.close();
}

首先调用 connect() 函数完成 TCP 的三次握手,建立与主机的连接,接着使用 write() 函数手动构造请求报文。请求报文的格式如下图所示,其中请求行的方法是 GET,URI 为请求路径 path,Http 协议版本为 HTTP/1.1,而首部行必须含有一个 Host 键值对指明将要连接的主机:

CS144 计算机网络 Lab0:Networking Warmup

发送完请求报文后就可以结束写操作,并不停调用 TCPSocket.read() 函数读取响应报文的内容直至结束,最后关闭套接字释放资源。其实这里也可以不手动关闭,因为 socket 对象被析构的时候会自动调用 FDWrapper.close() 释放文件描述符。

在命令行中输入下述命令完成编译:

mkdir build
cd build
cmake ..
make -j8

之后运行 ./apps/webget cs144.keithw.org /hello 就能看到响应报文了:

CS144 计算机网络 Lab0:Networking Warmup

接着运行测试程序,也顺利通过了:

CS144 计算机网络 Lab0:Networking Warmup

in-memory reliable byte stream

任务二要求我们实现一个内存内的有序可靠字节流:

  • 字节流可以从写入端写入,并以相同的顺序,从读取端读取
  • 字节流是有限的,写者可以终止写入。而读者可以在读取到字节流末尾时,不再读取。
  • 字节流支持流量控制,以控制内存的使用。当所使用的缓冲区爆满时,将禁止写入操作。
  • 写入的字节流可能会很长,必须考虑到字节流大于缓冲区大小的情况。即便缓冲区只有1字节大小,所实现的程序也必须支持正常的写入读取操作。
  • 在单线程环境下执行,无需考虑多线程生产者-消费者模型下各类条件竞争问题。

由于写入顺序和读出顺序相同,这种先入先出的 IO 特性可以使用队列来实现。C++ 标准库提供了 std::queue 模板类,但是 std::queue 不支持迭代器,这会对后续编码造成一点麻烦,所以这里换成双端队列 std::deque

类声明如下所示,使用 deque<char> 存储数据,_capacity 控制队列长度,_is_input_end 代表写入是否结束:

class ByteStream {
  private:
    size_t _capacity;
    std::deque<char> _buffer{};
    size_t _bytes_written{0};
    size_t _bytes_read{0};
    bool _is_input_end{false};
    bool _error{};  //!< Flag indicating that the stream suffered an error.

  public:
    //! Construct a stream with room for `capacity` bytes.
    ByteStream(const size_t capacity);

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

    //! 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 vector of bytes read
    std::string read(const size_t len) {
        const auto ret = peek_output(len);
        pop_output(len);
        return ret;
    }

    //! \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;

    //! Total number of bytes written
    size_t bytes_written() const;

    //! Total number of bytes popped
    size_t bytes_read() const;
};

类实现:

ByteStream::ByteStream(const size_t capacity) : _capacity(capacity) {}

size_t ByteStream::write(const string &data) {
    size_t ws = min(data.size(), remaining_capacity());

    for (size_t i = 0; i < ws; ++i)
        _buffer.push_back(data[i]);

    _bytes_written += ws;
    return ws;
}

//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
    auto rs = min(buffer_size(), len);
    return {_buffer.begin(), _buffer.begin() + rs};
}

//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
    auto rs = min(len, buffer_size());
    _bytes_read += rs;
    for (size_t i = 0; i < rs; ++i)
        _buffer.pop_front();
}

void ByteStream::end_input() { _is_input_end = true; }

bool ByteStream::input_ended() const { return _is_input_end; }

size_t ByteStream::buffer_size() const { return _buffer.size(); }

bool ByteStream::buffer_empty() const { return _buffer.empty(); }

bool ByteStream::eof() const { return buffer_empty() && input_ended(); }

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 _capacity - buffer_size(); }

之后重新 make -j8 编译,make check_lab0 的测试结果如下,也是成功通过了全部的测试用例:

CS144 计算机网络 Lab0:Networking Warmup

后记

由于 Lab0 只是个热身实验,所以整体而言还是比较简单的,通过这个实验,可以加深对 Http 请求报文结构的理解,同时对 C++ 的 RAII 机制也会有更直观的认识,以上~~文章来源地址https://www.toymoban.com/news/detail-417813.html

到了这里,关于CS144 计算机网络 Lab0:Networking Warmup的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • CS144(2023 Spring)Lab 0:networking warmup(环境搭建 & webget & bytestream)

    最近心情非常郁闷,搓一个CS144玩玩吧,正好2023 spring出新版了。。。CS144的头4个Lab(加上0是5个),一步步实现了一个TCP。在开始之前,我想贴一下Lab中的这句话: The lab documents aren’t “specifications”—meaning they’re not intended to be consumed in a one-way fashion. They’re written closer

    2024年02月11日
    浏览(45)
  • CS 144 Lab Four 收尾 -- 网络交互全流程解析

    对应课程视频: 【计算机网络】 斯坦福大学CS144课程 本节作为Lab Four的收尾,主要带领各位来看看网络交互的整体流程是怎样的。 这里以tcp_ipv4.cc文件为起点,来探究一下cs144是如何实现整个协议栈的。 首先,项目根路径中的 tun.sh 会使用 ip tuntap 技术创建虚拟 Tun/Tap 网络设备

    2024年02月04日
    浏览(42)
  • 北京大学计算机网络lab1——MyFTP

    目录 Lab目标 一、知识补充 二、具体实现 1.数据报文格式和字符串处理 2.open函数 3.auth 4.ls 5.get和put 三、总结 ps:本人靠着计网lab几乎就足够在就业行情并不好的23年找到自己满意的工作了,计网lab的教程也非常给力,对我这种恐惧写lab的菜狗都非常友好(本人写lab3确实比较

    2024年02月07日
    浏览(54)
  • 《计算机网络自顶向下》Wireshark实验 Lab4 TCP

    《计算机网络自顶向下》Wireshark Lab + 套接字编程作业 + 杂项实验室编程作业 全实验博客链接 各位好 啊 学计算机想要学好真的还是挺难的 说实话 科班学计算机如果想要花大量的时间去学 只能把平时的课程大部分时间不去上 如果班级管理严格或者说 各种因素让你不得不去上

    2023年04月09日
    浏览(76)
  • Wireshark HTTP实验—Wireshark Lab: HTTP v7.0(计算机网络自顶向下第七版)

    Is your browser running HTTP version 1.0 or 1.1? What version of HTTP is the server running? 浏览器与服务器的版本均为 H T T P / 1.1 HTTP/1.1 H TTP /1.1 。 What languages (if any) does your browser indicate that it can accept to the server? 能接受简体中文以及英文。 What is the IP address of your computer? Of the gaia.cs.umass.edu serv

    2024年02月08日
    浏览(40)
  • Wireshark IP实验—Wireshark Lab: IP v7.0(计算机网络自顶向下第七版)

    修改发送数据包的大小 跟踪的地址为 www.ustc.edu.cn text{www.ustc.edu.cn} www.ustc.edu.cn 由于自己抓的包比较凌乱,分析起来比较复杂,所以使用作者的数据包进行分析 Select the first ICMP Echo Request message sent by your computer, and expand the Internet Protocol part of the packet in the packet details window.Wh

    2024年02月04日
    浏览(38)
  • Wireshark TCP实验—Wireshark Lab: TCP v7.0(计算机网络自顶向下第七版)

    What is the IP address and TCP port number used by the client computer (source) that is transferring the file to gaia.cs.umass.edu? 根据数据包中的 tcp-ethereal-trace-1 ,其源 IP 地址为 192.168.1.102 192.168.1.102 192.168.1.102 ,端口号为 1162 1162 1162 。 What is the IP address of gaia.cs.umass.edu? On what port number is it sending and re

    2023年04月09日
    浏览(45)
  • CS144-Lab6

    在本周的实验中,你将在现有的 NetworkInterface 基础上实现一个IP路由器,从而结束本课程。路由器有几个网络接口,可以在其中任何一个接口上接收互联网数据报。路由器的工作是根据 路由表 转发它得到的数据报:一个规则列表,它告诉路由器,对于任何给定的数据报: 发

    2024年02月09日
    浏览(44)
  • CS144--Lab1笔记

    CS144——Lab1笔记 作为使用了版本管理的项目,开始新的开发,当然先要新建一个开发分支啦,可以用命令或者直接在IDE中GIT的图形控制界面操作,太简单就不细说。(我习惯命名:dev-lab1) 首先要从原始仓库合并Lab1的相关文件到本地仓库,接着在build目录下编译。执行下面的

    2024年02月20日
    浏览(53)
  • CS 144 Lab One -- 流重组器

    对应课程视频: 【计算机网络】 斯坦福大学CS144课程 Lab 1 对应的PDF: Lab Checkpoint 1: stitching substrings into a byte stream 这幅图完整的说明了CS144 这门实验的结构: 其中, ByteStream 是我们已经在 Lab0 中实现完成的。 我们将在接下来的实验中分别实现: Lab1 StreamReassembler :实现一个流

    2024年02月16日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包