Set up GNU/Linux on your computer
我用的是Ubuntu,按照指导书上写的输入如下命令安装所需的软件包:
bash
sudo apt update && sudo apt install git cmake gdb build-essential clang \
clang-tidy clang-format gcc-doc pkg-config glibc-doc tcpdump tshark
Networking by hand
主要任务包括:检索网页和发送电子邮件,这两者都依赖一种可靠的双向字节流。
Fetch a Web page
没有账号密码,所以这部分只做记录。
Telnet是一种应用层协议,使用于互联网及局域网中,使用虚拟终端的形式,提供双向、以文字字符串为主的命令行接口交互功能。
HTTP协议(Hyper Text Transfer Trotocol)从万维网传输超文本到本地浏览器的传送协议。
bash
telnet cs144.keithw.org http
建立连接
HTTP协议的GET请求,从指定的资源请求数据。
bash
GET /hello HTTP/1.1
告诉服务器请求资源的URL
bash
Host: cs144.keithw.org
告诉服务器URL的主机部分
bash
Connection: close
关闭连接。
Send yourself an email
简单邮件发送协议(英语:SimpleMailTransferProtocol,缩写:SMTP)可用在发送和接收电子邮件的信息,但SMTP通常用作发送电子邮件信息,而不是接收。
bash
telnet 148.163.153.234 smtp
Listening and connecting
netcat用于任意 TCP 与 UDP 连接和侦听:
-v 选项表示"verbose mode"(详细模式)
-l 选项表示"listen mode"(监听模式)。
-p 选项表示"port"或"local port"(本地端口)。
bash
netcat -v -l -p 9090
Writing a network program using an OS stream socket
目标 :使用Linux以及大多数其他操作系统都提供的功能:创建双向字节流、一个在自己的主机上运行,另一个在Internet的另一台主机上运行。
这个特性被叫做流套接字,但是Internet并不提供可靠的字节流服务,Internet做的唯一事情就是尽最大努力将数据报文发送到目的地。
本实验要实现一个名为"webget"的程序,创建一个TCP流套接字,连接到Web服务器并获取页面。
Let's get started---fetching and building the starter code
这里的编译需要注意C++用的是C++20,我的默认C++是17会报错。
cpp
cmake -S .-B build #-S指定源代码目录 -B指定生成目录
cmake --build build # --build指定构建目录
Modern C++: mostly safe but still fast and low-level
- 不使用
malloc()
和free()
; - 不使用new 和delete;
- 不要使用裸指针,必要的时候使用智能指针
unique_ptr
或者shared_ptr
; - 避免使用模板、线程、锁和虚函数;
- 避免使用C风格的字符串,使用std::string;
- 避免使用C风格的强制转换,如果必须使用,使用C++的
static_cast
; - 最好使用const引用传递参数 e.g.:const Address & address;
- 使每个变量都变const,除非它能被改变;
- 使每个函数都变const,除非它需要改变对象;
- 避免使用全局变量,让每个变量尽可能小的作用域;
Writing webget
实验指导书里说要用到TCPSocket和Address 类
Hint:
在HTTP中每行必须是以\r\n结尾。
在客户端请求包含"Connection: close",当客户端读取来自服务器端的"EOF",这表示服务器端已经回复完成。
确保读取并打印服务器的所有输出,直到套接字到达"EOF"(文件末尾),一次read调用是不够的。
大约十行代码。
lab0用到的函数在下面的网站都能查到。
官方文档
cpp
void get_URL( const string& host, const string& path )
{
cerr << "Function called: get_URL(" << host << ", " << path << ")\n";
// cerr << "Warning: get_URL() has not been implemented yet.\n";
// 创建TCPSocket
TCPSocket sock;
sock.connect( Address( host, "http" ) );
// 构造 HTTP GET 请求
string request = "GET " + path + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n";
sock.write( request );
// 接收响应
string res;
while ( !sock.eof() ) {
sock.read( res );
cout << res;
}
sock.close();
}
在根目录下输入cmake --build build --target check_webget
结果如下:
An in-memory reliable byte stream
实验要求:
- 字节在输入端写入,并以相同顺序在输出端被读取;字节流是有限的,writer可以停止写入,reader在读到EOF后不再读入。
- 字节流需要流控制,容量将限制writer能够在给定时刻最多写入的数据量,当reader读取数据时,writer可以写的更多。(所以网上很多方案选择双端队列解决,当然deque可能会造成更大的内存开销,姑且这么实现)。
byte_stream.hh
cpp
class ByteStream
{
public:
explicit ByteStream( uint64_t capacity );
// Helper functions (provided) to access the ByteStream's Reader and Writer interfaces
Reader& reader();
const Reader& reader() const;
Writer& writer();
const Writer& writer() const;
void set_error() { error_ = true; }; // Signal that the stream suffered an error.
bool has_error() const { return error_; }; // Has the stream had an error?
protected:
// Please add any additional state to the ByteStream here, and not to the Writer and Reader interfaces.
uint64_t capacity_;
bool error_ {};
// my code here
bool closed = false;
uint64_t total_bytes_pushed = 0;
uint64_t total_bytes_poped = 0;
std::deque<char> buffer = {};
};
byte_stream.cc
cpp
#include "byte_stream.hh"
using namespace std;
ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity ) {}
// 返回stream是否关闭
bool Writer::is_closed() const
{
// Has the stream been closed?
// Your code here.
return closed;
}
// Writer将数据放入stream中
void Writer::push( string data )
{
// Your code here.
// (void)data;
uint64_t len = data.length();
if ( len > capacity_ - buffer.size() ) {
len = capacity_ - buffer.size();
}
for ( uint64_t i = 0; i < len; ++i ) {
buffer.push_back( data[i] );
total_bytes_pushed++;
}
return;
}
// 关闭stream
void Writer::close()
{
closed = true;
// Your code here.
}
// 返回capacity - 已经用过的stream大小
uint64_t Writer::available_capacity() const
{
// Your code here.
return capacity_ - buffer.size();
}
// 返回总的push进stream的字节数
uint64_t Writer::bytes_pushed() const
{
// Your code here.
return total_bytes_pushed;
}
// 返回stream是否关闭或者pop完所有的元素
bool Reader::is_finished() const
{
// Your code here.
return closed && buffer.empty();
}
// 返回总的pop的stream的字节数
uint64_t Reader::bytes_popped() const
{
// Your code here.
return total_bytes_poped;
}
// Peek at the next bytes in the buffer
// string_view: C++ 17引入,在不拷贝的情况下读取、查看和操作字符串
// peek函数作用:
string_view Reader::peek() const
{
// Your code here.
if ( !buffer.empty() ) {
return std::string_view( &buffer.front(), 1 ); // 返回deque的front元素的string_view
}
return std::string_view(); // 返回一个默认构造的string_view(空的)
}
//
void Reader::pop( uint64_t len )
{
// Your code here.
if ( buffer.size() < len ) {
len = buffer.size();
}
for ( uint64_t i = 0; i < len; ++i ) {
buffer.pop_front();
total_bytes_poped++;
}
}
// Number of bytes currently buffered (pushed and not popped)
uint64_t Reader::bytes_buffered() const
{
// Your code here.
return buffer.size();
}
运行结果: