Stanford CS144 Lab0 小结

目录

本文为斯坦福大学计算机网络课程 CS144 编程任务 Lab Assignment 0 的学习小结

官网 https://cs144.github.io/

Lab 0 文档 https://cs144.github.io/assignments/lab0.pdf

个人实验备份代码 https://github.com/deepzheng/sponge


环境的安装与配置

官方文档提供了三种支持运行该 Lab 的选择

我还是比较推荐使用课程组提供的 VirtualBox 的镜像的

本来我嫌麻烦,用自己的 Vmware + UBuntu20.04 自己来配置环境,结果在做到第三步运行测试代码 make check_webget 的时候发现报出了离奇的错误。把代码还原到改动之前依然报错,翻遍全网也没有找到解决办法。

是CMake出了问题??不太清楚

最后只能灰溜溜地下载了 VirtualBox 和课程组的 LUBuntu 镜像。之前从没用过lUBuntu 的我看到类 Windows 的界面还是有点别扭2333 好在基本操作大差不差

Networking by hand

这部分内容比较简单,跟着文档一步步做就好了

Fetch a Web page

使用 telnet 发送 http 请求,命令结束后需要两次回车结束请求

Send youself an email

官方文档提供的是斯坦福校内的邮箱服务器,想要完成这个部分就需要使用国内的邮箱服务器了,并且操作上会稍微麻烦一点

我选用的是 QQ 的邮箱服务器,在登陆的时候需要提供账号和校验码的 Base64编码

Listening and connecting

开两个终端分别模拟客户端和服务器端操作。服务器端开启监听模式,等待客户端建立连接之后,就可以收到另一个终端发来的消息了

Writing a network program using an OS stream socket

这一个部分是本 Lab 的核心,需要使用 OS 流套接字编写实现一个get_URL函数以及完成一个字节流控制读写类

Writing webget

模拟上面 telnet 的操作,使用套接字编写 get_URL 函数实现 webget 。

web_get.cc

void get_URL(const string &host, const string &path) {
    // Your code here.
    TCPSocket sock{};
    sock.connect(Address(host,"http"));
    string message = "GET " + path + " HTTP/1.1\r\nHOST: " + host + "\r\n\r\n";
    sock.write(message);
    sock.shutdown(SHUT_WR);
    while( !sock.closed() && !sock.eof()){
        //通过 eof 判断是否已经读到了最末尾,若没有则读取继续
        cout << sock.read();
    }
    sock.close();
    return ;

    cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
    cerr << "Warning: get_URL() has not been implemented yet.\n";
}

测试通过

tips:

  • TCP 在建立连接时会由系统隐形 bind 端口,不需要像 UDP 一样手动操作
  • 相比于close(),shutdown() 提供了一个更为优雅的操作方式。调用 close() 函数意味着完全断开连接,即不能发送数据也不能接收数据。而 shutdown() 提供了三个变量取值
    • SHUT_RD 断开输入流
    • SHUT_WR 断开输出流
    • SHUT_RDWR 同时断开 I/O 流
  • 默认情况下,调用 close() 后无论输入缓冲区中是否有数据都将立即关闭套接字,后续需要发送数据需要再次建立套接字连接。而shutdown() 只用于关闭连接而非套接字,并且 shutdown() 会等待输入缓冲区中的数据传输完成后再关闭连接

An in-memory reliable byte stream

该部分需要实现一个简单的可靠字节流类,支持写入、读出、容量控制、流量计算

对于字节流缓冲区的数据结构,本来我是想使用string来进行双向读写,但是按照现代C++d的要求,应该尽可能避免使用指针,而 string 下标的索引本质上还是指针,所以我最后使用了 deque 双向队列。双向队列支持头尾读写,刚好对应字节流从尾部写入从头部读取的特性,并且拥有迭代器,完美支持了该字节流类中的 peek 操作

byte_stream.hh

#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH

#include <string>
#include <deque>

//! 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.    
    std::deque <char> Stream = {};
    size_t Capacity = 0;
    size_t written_byte = 0;
    size_t read_byte = 0;
    bool is_end_input = false;
    // 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:
    //这部分不需要编辑,略去

#endif  // SPONGE_LIBSPONGE_BYTE_STREAM_HH

byte_stream.cc

#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`
using namespace std;

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

// 向队列中写入data
size_t ByteStream::write(const string &data) {
    size_t write_length = data.length();
    if(write_length > Capacity - Stream.size()){
        //compared with left room
        write_length = Capacity - Stream.size();
    }
    for(size_t i = 0;i < write_length;i++){
        Stream.emplace_back(data[i]);
    }
    written_byte += write_length;
    return write_length;
}

//! \param[in] len bytes will be copied from the output side of the buffer
// 从头部读取 len 长度的字节
string ByteStream::peek_output(const size_t len) const {
    string output_string;
    for(size_t i = 0;i < len && i < Stream.size();i++){
        output_string.push_back(Stream.at(i));
    }
    return output_string;
}

//! \param[in] len bytes will be removed from the output side of the buffer
// 将头部 len 长度字节移除字节流
void ByteStream::pop_output(const size_t len) {
    size_t pop_length = len > Stream.size() ? Stream.size(): len;
    read_byte += pop_length;
    while(pop_length --){
        Stream.pop_front();
    }
}

//! 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
// 读取(相当于peek和pop操作的结合)
std::string ByteStream::read(const size_t len) {
    size_t read_length = len > Stream.size() ? Stream.size() : len;
    string read_string = peek_output(read_length);
    pop_output(read_length);
    return read_string;
}

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

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

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

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

bool ByteStream::eof() const { return Stream.empty() && is_end_input; }

size_t ByteStream::bytes_written() const { return written_byte ; }

size_t ByteStream::bytes_read() const { return read_byte; }

size_t ByteStream::remaining_capacity() const { return Capacity - Stream.size(); }

测试通过

任务结束,收工大吉

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦