Stanford CS144 Lab 0

Lab Checkpoint 0: networking warmup

寒假在几个不同的地方刷到这门课的介绍,想在 25 春跟着过一遍,最后显然是没有跟。三伏天的中伏,偶然想起来,决定在暑假把 lab 做了,也算找回一点失去的热情。

1 Set up GNU/Linux on your computer #

Debian 12 on WSL2,没啥说的。

lab 框架用了一些 modern C++ 特性,需要 gcc > 13,于是自己编译了,详见上一篇文章。

2 Networking by hand #

2.1 Fetch a Web page #

输了我的清华学号居然也能给我一个 secret,可见这个东西是按某种规则计算出来的。

➜  ~ telnet cs144.keithw.org http
Trying 104.196.238.229...
Connected to cs144.keithw.org.
Escape character is '^]'.
GET /lab0/2023XXXXXX HTTP/1.1
Host: cs144.keithw.org
Connection: close

HTTP/1.1 200 OK
Date: Wed, 30 Jul 2025 14:35:08 GMT
Server: Apache
X-You-Said-Your-SunetID-Was: 2023XXXXXX
X-Your-Code-Is: 593860
Content-length: 114
Vary: Accept-Encoding
Connection: close
Content-Type: text/plain

Hello! You told us that your SUNet ID was "2023XXXXXX". Please see the HTTP headers (above) for your secret code.
Connection closed by foreign host.

2.2 Send yourself a email #

手敲 SMTP 协议,但我又不是斯坦福学生,告辞。

2.3 Listening and connecting #

sudo apt install netcat-traditional

3 Writing a network program using an OS stream socket #

3.5 Writing webget #

void get_URL( const string& host, const string& path )
{
  Address address { host, "http" };
  TCPSocket socket;
  socket.connect( address );
  socket.set_reuseaddr();
  socket.write( "GET " + path + " HTTP/1.1\r\n" );
  socket.write( "Host: " + host + "\r\n" );
  socket.write( "Connection: close\r\n" );
  socket.write( "\r\n" );

  string response;
  // read until EOF
  while ( !socket.eof() ) {
    string buffer;
    socket.read( buffer );
    response += buffer;
  }
  cout << response;
  socket.close();
}

这里设置了 SO_REUSEADDR flag 用来允许地址重用,也就是说允许绑定到一个存在已建立连接的地址,除非另一个 socket 正在监听它。可能会存在这种情况:服务器启动后,有客户端连接并已建立,如果服务器主动关闭,那么和客户端的连接会处于 TIME_WAIT 状态。如果没有设置地址重用,再次启动服务器时,就会因 Address already in use 而启动失败。

4 An in-memory reliable byte stream #

首先用 std::string 实现了一个 naive 版的:

std::string buffer_ {};

void Writer::push( string data )
{
  uint64_t space_available = available_capacity();
  uint64_t len = min( space_available, static_cast<uint64_t>(data.size()) );
  if ( len > 0 ) {
    buffer_.append( data, 0, len );
    bytes_pushed_ += len;
  }
}

string_view Reader::peek() const
{
  return {buffer_.data(), buffer_.size()};
}

void Reader::pop( uint64_t len )
{
  len = min( len, static_cast<uint64_t>(buffer_.size()) );
  bytes_popped_ += len;
  buffer_.erase( 0, len );
}
Test project /home/rhdu/cs144-minnow-25winter/build
      Start  1: compile with bug-checkers
 1/11 Test  #1: compile with bug-checkers ........   Passed    4.20 sec
      Start  2: t_webget
 2/11 Test  #2: t_webget .........................   Passed    1.83 sec
      Start  3: byte_stream_basics
 3/11 Test  #3: byte_stream_basics ...............   Passed    0.01 sec
      Start  4: byte_stream_capacity
 4/11 Test  #4: byte_stream_capacity .............   Passed    0.01 sec
      Start  5: byte_stream_one_write
 5/11 Test  #5: byte_stream_one_write ............   Passed    0.01 sec
      Start  6: byte_stream_two_writes
 6/11 Test  #6: byte_stream_two_writes ...........   Passed    0.01 sec
      Start  7: byte_stream_many_writes
 7/11 Test  #7: byte_stream_many_writes ..........   Passed    0.04 sec
      Start  8: byte_stream_stress_test
 8/11 Test  #8: byte_stream_stress_test ..........   Passed    0.02 sec
      Start 37: no_skip
 9/11 Test #37: no_skip ..........................   Passed    0.00 sec
      Start 38: compile with optimization
10/11 Test #38: compile with optimization ........   Passed    2.19 sec
      Start 39: byte_stream_speed_test
        ByteStream throughput (pop length 4096): 31.69 Gbit/s
        ByteStream throughput (pop length 128):   6.86 Gbit/s
        ByteStream throughput (pop length 32):    1.53 Gbit/s
11/11 Test #39: byte_stream_speed_test ...........   Passed    0.19 sec

100% tests passed, 0 tests failed out of 11

Total Test time (real) =   8.51 sec
Built target check0

我想看看怎么提高性能,于是用 std::vector 写了一个 ring buffer:

std::vector<char> buffer_; // Ring buffer
uint64_t head_ {};  // Read position
uint64_t tail_ {};  // Write position
uint64_t size_ {};  // Current data size

void Writer::push( string data )
{
  uint64_t space_available = available_capacity();
  uint64_t len = min( space_available, static_cast<uint64_t>(data.size()) );
  if (tail_ + len <= buffer_.size())
  {
    copy(data.data(), data.data() + len, buffer_.data() + tail_);
  }
  else
  {
    copy(data.data(), data.data() + (buffer_.size() - tail_), buffer_.data() + tail_);
    copy(data.data() + (buffer_.size() - tail_), data.data() + len, buffer_.data());
  }
  tail_ = (tail_ + len) % buffer_.size();
  bytes_pushed_ += len;
  size_ += len;
}

string_view Reader::peek() const
{
  if (size_ == 0)
    return {};
  if (head_ + size_ <= buffer_.size())
    return {buffer_.data() + head_, size_};
  else
  {
    static string tmp;
    tmp.resize(size_);
    copy(buffer_.data() + head_, buffer_.data() + buffer_.size(), tmp.data());
    copy(buffer_.data(), buffer_.data() + ((head_ + size_) % buffer_.size()), tmp.data() + (buffer_.size() - head_));
    return {tmp.data(), tmp.size()};
  }
}

void Reader::pop( uint64_t len )
{
  len = min( len, size_ );
  head_ = (head_ + len) % buffer_.size();
  bytes_popped_ += len;
  size_ -= len;
}

然而其速度十分拉跨,猜测可能是因为访存不连续以及大段 copy 导致的。

Test project /home/rhdu/cs144-minnow-25winter/build
      Start  1: compile with bug-checkers
 1/11 Test  #1: compile with bug-checkers ........   Passed    1.08 sec
      Start  2: t_webget
 2/11 Test  #2: t_webget .........................   Passed    1.87 sec
      Start  3: byte_stream_basics
 3/11 Test  #3: byte_stream_basics ...............   Passed    0.01 sec
      Start  4: byte_stream_capacity
 4/11 Test  #4: byte_stream_capacity .............   Passed    0.01 sec
      Start  5: byte_stream_one_write
 5/11 Test  #5: byte_stream_one_write ............   Passed    0.01 sec
      Start  6: byte_stream_two_writes
 6/11 Test  #6: byte_stream_two_writes ...........   Passed    0.01 sec
      Start  7: byte_stream_many_writes
 7/11 Test  #7: byte_stream_many_writes ..........   Passed    0.04 sec
      Start  8: byte_stream_stress_test
 8/11 Test  #8: byte_stream_stress_test ..........   Passed    0.02 sec
      Start 37: no_skip
 9/11 Test #37: no_skip ..........................   Passed    0.00 sec
      Start 38: compile with optimization
10/11 Test #38: compile with optimization ........   Passed    0.53 sec
      Start 39: byte_stream_speed_test
        ByteStream throughput (pop length 4096): 31.31 Gbit/s
        ByteStream throughput (pop length 128):   2.92 Gbit/s
        ByteStream throughput (pop length 32):    0.78 Gbit/s
11/11 Test #39: byte_stream_speed_test ...........   Passed    0.26 sec

100% tests passed, 0 tests failed out of 11

Total Test time (real) =   3.84 sec
Built target check0

最后用 std::deque 实现了一个,速度最慢,就不放代码了。