【计算机网络】CS144 Lab0攻略

120 阅读4分钟

449782541_356761153879645_1206453802788089775_n.jpg

实验指导书下载:cs144.github.io/assignments…

2 Networking by hand

2.1 Fetch a Web page

非常简单的热身活动,通过手工设置http请求头并发送请求的方式,帮助我们快速熟悉Linux环境中telnet工具的基本使用。

唯一需要注意的是,敲命令的时候尽量要快点儿,不然可能在敲完命令之前tcp连接就断开了。

顺次执行如下命令即可:

telnet cs144.keithw.org http
GET /hello HTTP/1.1
Host: cs144.keithw.org
Connection: close

对比实际浏览器发送的请求头,可以看到它会复杂得多:

image.png

在telnet中http请求的响应结果如下:

image.png 对比在浏览器中直接访问目标URL的结果,可以看到除了Connection字段外http请求的响应头和刚才用telnet手工测试的结果是基本一样的:

image.png

在HTTP/1.1协议中,若发送请求时请求头不携带Connection字段,则服务端程序取默认值keep-alive。这就是我们在浏览器调试工具和telnet中看到响应头中Connection字段内容不同的原因。

2.2 Send yourself an email

这里我采用我自己的163邮箱账号进行演示。首先为了确保telnet能够透过163邮箱的服务器发送邮件,我们需要先在其网站上开启SMTP服务并申请一个由16个大写英文字母组成的授权密码。

image.png

接下来在shell中执行命令,连接163的smtp服务器,并与服务器打个招呼:

telnet smtp.163.com smtp
HELO wu-sunflower

HELO后面的内容我们可以随便填,一般服务器不会进行检查。

然后我们要配合服务端进行身份校验。我们将自己要准备发送邮件的邮箱地址(xxx@163.com),以及163网站提供的长度为16的授权密码,分别转换成base64编码,然后提交给smtp服务器:

AUTH LOGIN
填你自己的邮箱地址的base64编码
填你自己的授权密码的base64编码

现在我们填写发送者和收件人邮箱地址,以及邮件的正文部分(本实验中只包括FromToSubject这三个字段)。完成后我们关闭连接:

MAIL FROM: <xxx@163.com>
RCPT TO: <xxx@163.com>
DATA
From: xxx@163.com
To: xxx@163.com
Subject: Hello from CS144 Lab 0!
.
QUIT

似乎有些邮件服务商要求MAIL FROMRCPT TO后面跟的邮箱地址要用尖括号包起来,有些则不用。

截图如下:

image.png

如果能够在邮箱中收到刚才发送的邮件,那么就说明实验成功了:

image.png

2.3 Listening and connecting

体验tcp连接中客户端和服务端之间的双工通信,没什么好讲的。

image.png

3 Writing a network program using an OS stream socket

编写代码,模拟手工使用telnet发送http请求的过程。只要搞清楚了相关类及其方法的用法,本题没有任何难度。唯一需要注意的是,当http请求头全部写进套接字之后,还需要再多写入一个\r\n,以告诉web服务器http请求已经发送完毕!

// apps/webget.cc
void get_URL( const string& host, const string& path ) {
  Address my_address(host, "http");
  TCPSocket my_socket;
  my_socket.connect(my_address);

  string str_command = "GET " + path + " HTTP/1.1\r\n";
  string str_host = "Host: " + host + "\r\n";
  string str_connection = "Connection: close\r\n\r\n";
  my_socket.write(str_command);
  my_socket.write(str_host);
  my_socket.write(str_connection);
  
  while (!my_socket.eof()) {
    string my_buffer = "";
    my_socket.read(my_buffer);
    cout << my_buffer;
  }
  
  my_socket.close();
}

测试效果如下:

image.png

image.png

4 An in-memory reliable byte stream

这里我采用最简单的FIFO队列(c++ stl库已提供实现)来模拟字节流的行为。

src/byte_stream.hh中修改如下部分的代码,添加支撑字节流实现的成员变量:

class ByteStream
{
  // ...
protected:
  // Please add any additional state to the ByteStream here, and not to the Writer and Reader interfaces.
  uint64_t capacity_;
  uint64_t bytes_pushed_ {};
  uint64_t bytes_buffered_ {};
  uint64_t bytes_popped_ {};
  uint64_t bytes_remove_prefix_ {};
  std::queue<std::string> stream_ {};
  bool error_ {};
  bool is_closed_ {};
};

src/byte_stream.cc中的代码如下:

#include "byte_stream.hh"

using namespace std;

ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity ) {}

bool Writer::is_closed() const
{
  return is_closed_;
}

void Writer::push( string data )
{
  uint64_t cur_capacity = available_capacity();
  if (is_closed_ || cur_capacity == 0 || data.empty()) {
    return;
  }
  if (cur_capacity < data.size()) {
    data.resize(cur_capacity);
  }
  bytes_buffered_ += data.size();
  bytes_pushed_ += data.size();
  stream_.emplace(std::move(data));
}

void Writer::close()
{
  is_closed_ = true;
}

uint64_t Writer::available_capacity() const
{
  return capacity_ - bytes_buffered_;
}

uint64_t Writer::bytes_pushed() const
{
  return bytes_pushed_;
}

bool Reader::is_finished() const
{
  return is_closed_ && bytes_buffered_ == 0;
}

uint64_t Reader::bytes_popped() const
{
  return bytes_popped_;
}

string_view Reader::peek() const
{
  return stream_.empty() ? 
    string_view {} 
    : string_view {stream_.front()}.substr(bytes_remove_prefix_);
}

void Reader::pop( uint64_t len )
{
  while (len != 0U && bytes_buffered_ != 0U && !stream_.empty()) {
      uint64_t front_size = stream_.front().size() - bytes_remove_prefix_;
      if (len < front_size) {
        bytes_remove_prefix_ += len;
        bytes_buffered_ -= len;
        bytes_popped_ += len;
        break;
      }
      stream_.pop();
      bytes_remove_prefix_ = 0;
      bytes_buffered_ -= front_size;
      bytes_popped_ += front_size;
      len -= front_size;
  }
}

uint64_t Reader::bytes_buffered() const
{
  return bytes_buffered_;
}