本文最后更新于:2024年3月23日 下午
因为一些个人安排的原因,决定先学一下network再回头写6.1810,于是开始了这个非常棒的Stanford的lab。课程官网在https://cs144.github.io/ , 会随着时间更新,如果本学期课结束了的话我考虑把实验要求的pdf放上来。
似乎这个课今年加了很多东西,尤其是C++23的新特性,所以学起来很新鲜。当然,笔者的水平完全达不到Modern C++的要求,所以很多代码肯定会有疏漏。
实验环境为WSL Debian + gcc13.1,前面的部分略过,从代码开始
语法 这里出现了一些很新的cpp语法特性(起码对我来说很新), string_view 和 static_cast
C++17中我们可以使用std::string_view来获取一个字符串的视图,类似于Golang的slice。字符串视图并不真正的创建或者拷贝字符串,而只是拥有一个字符串的查看功能。显然大部分的字符串操作不需要真正拥有这个字符串,所以这个C++风格的string ref可以便捷地提高性能。参阅
可以看到在下文webget的调用中通过span返回了一个对参数的view。
static_cast是一个C++风格的类型转换,与其他类型转换的区别参阅什么时候应该使用static_cast、dynamic_cast、const_cast和reinterpret_cast?
Webget 其实是一个源码阅读练习,所以才说We expect you’ll need to write about ten lines of code.(笑
我们发现Address类的构造函数长这样
1 Address ( const std::string& hostname, const std::string& service );
发现TCPSocket继承了父类Socket的connect方法,之后进行系统调用来解析地址
1 void connect ( const Address& address ) ;
事实上socket是fd的子类,但现在不重要、
所以我们的webget就很自然的写好了
似乎这里需要一些科学上网(?,我的网络配置是wsl的mirrored mode和clash for win的TUN mode。接管了所有对外的网络请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 void get_URL ( const string& host, const string& path ) { Address addr (host,"http" ) ; TCPSocket sock; sock.connect (addr); string req = "GET " + path + " HTTP/1.1\r\nHost: " + host + "\r\n" + "Connection: close\r\n\r\n" ; sock.write (req); string buf; while (!sock.eof ()){ sock.read (buf); cout << buf; } }
An in-memory reliable byte stream 要求类似于写一个pipe,然后输入输出。不需要考虑并发。
缓冲区使用vector或者string都是无所谓的,只要维护好size和capacity就可以。
首先在父类添加需要的protected成员变量(注释已经告诉你怎么加了qwq
1 2 3 4 5 6 7 8 9 10 class ByteStream {protected : ··· bool is_closed_ {}; std::string buffer_ {}; uint64_t bytes_pushed_ {}; uint64_t bytes_popped_ {}; };
之后就把每个要求实现的都实现就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 ByteStream::ByteStream ( uint64_t capacity ) : capacity_ ( capacity ) { buffer_.reserve ( capacity ); }bool Writer::is_closed () const { return this ->is_closed_; }void Writer::push ( string data ) { size_t available_capacity = this ->available_capacity (); size_t bytes_to_copy = min (data.size (), available_capacity); buffer_ += data.substr (0 , bytes_to_copy); bytes_pushed_ += bytes_to_copy; }void Writer::close () { is_closed_ = true ; }uint64_t Writer::available_capacity () const { return capacity_ - buffer_.size (); }uint64_t Writer::bytes_pushed () const { return bytes_pushed_; }bool Reader::is_finished () const { return ((is_closed_ == true ) && (this ->bytes_buffered () == 0 )); }uint64_t Reader::bytes_popped () const { return bytes_popped_; }string_view Reader::peek () const { string_view sv ( buffer_.data() , min(static_cast <uint64_t >(1000 ),this ->bytes_buffered())) ; return sv; }void Reader::pop ( uint64_t len ) { buffer_.erase ( buffer_.begin () , buffer_.begin () + len ); bytes_popped_ += len; }uint64_t Reader::bytes_buffered () const { return buffer_.size (); }
因为我们在构造函数中处理好了string的capacity,所以在push的过程中就不需要考虑了。
这里卡我的点是peek,因为我当时望文生义了
1 std::string_view peek () const ;
所以我的peek一开始真的只返回了next byte( 后来speedtest过不了,只能跑0.03gbps。看到了read函数的实现,于是修改成了上面的样子,就好了。