前言
Lab0 有两个任务,第一个任务是实现能发送 Get 请求到任意网址的 webget 程序(Writing webget ),第二个任务是实现内存内的可靠字节流(An in-memory reliable byte stream)。
任务1:Writing webget
基本原理
下列代码能在Linux 系统中实现对一个web进行请求并返回内容(输入要快,且最后一下要多按一次回车)
Linux_cmd
telnet cs144.keithw.org http
GET /hello HTTP/1.1
Host: cs144.keithw.org
Connection: close
正常运行结果:
代码实现
该任务具体是要用代码实现在命令界面相同的结果
Socket可以认为是对一个文件进行读写操作。因此可以被抽象为一个文件描述符FileDescriptor
,在源码中TCP Socket、UDP Socket等Socket类继承FileDescriptor类。
头文件:util/file_descriptor.hh
文件描述符是计算机操作系统中用于标识和访问打开的文件或设备的整数值。在 Linux 中,内核通过文件描述符来找到每个文件,一个文件可以被许多用户同时打开或一个用户同时打开多次,为管理文件的当前位移量等问题,引入了文件描述符。
具体实现在apps/webget.cc
中需要补全void get_URL( const string& host, const string& path )
- 先构建连接
C++
Address const myaddress(host,"http");//host为主机地址,http为连接方式
TCPSocket mysocket;
mysocket.connect(myaddress);
- 构建并写入指令
C++
string const getcom = "GET "+path+" HTTP/1.1\r\n";
string const hostcom = "Host: "+host+"\r\n";
string const concom = "Connection: close\r\n\r\n";
mysocket.write(getcom);
mysocket.write(hostcom);
mysocket.write(concom);
- 读取返回的数据
C++
while(!mysocket.eof()) {//eof为bool终止条件
string buff="";
mysocket.read(buff);
cout<<buff;
}
mysocket.close();//关闭socket
总代码
C++
void get_URL( const string& host, const string& path )
{
Address const myaddress(host,"http");
TCPSocket mysocket;
mysocket.connect(myaddress);
string const getcom = "GET "+path+" HTTP/1.1\r\n";
string const hostcom = "Host: "+host+"\r\n";
string const concom = "Connection: close\r\n\r\n";
mysocket.write(getcom);
mysocket.write(hostcom);
mysocket.write(concom);
while(!mysocket.eof()) {
string buff="";
mysocket.read(buff);
cout<<buff;
}
mysocket.close();
}
控制台输入(因为我是Clion ssh连接,所以路径不同)
Linux_cmd
make
./apps/webget cs144.keithw.org /hello
下一步是通过测试案例,控制台输入 cmake --build build --target check webget
任务2:An in-memory reliable byte stream
实现一个可靠的字节流:
- 字节在输入端写入,在输出端输出保持相同顺序读取(队列)
- 可终止写入以及读取(EOF)
- 能够处理大容量字节流
Writer类:
C++
void push( std::string data );
// 功能:将数据推送到流中(注意范围)
void close();
// 功能:发出信号表示流已到达结尾
bool is_closed() const;
// 功能:判断流是否已经关闭
uint64_t available_capacity() const;
// 功能:字节流可写入容量
uint64_t bytes_pushed() const;
// 功能:查询累计推送到该流的字节总数是多少
Reader类:
C++
std::string_view peek() const;
// 功能:查看缓冲区中的下一批字节内容,但并不移除它们
//(std::string_view类型,可用于查看字节数据而无需复制)
void pop( uint64_t len );
// 功能:从缓冲区中移除`len`个字节
bool is_finished() const;
// 功能:判断流是否已经结束(即已关闭且所有数据都已被弹出)
bool has_error() const;
// 功能:判断流是否出现过错误
uint64_t bytes_buffered() const;
// 功能:查询当前在缓冲区中缓冲的字节数(即已推送但尚未弹出的字节数)
uint64_t bytes_popped() const;
// 功能:查询累计从流中弹出的字节总数
byte_stream.hh
则在src/byte_stream.hh
中可写入
C++
protected:
// Please add any additional state to the ByteStream here, and not to the Writer and Reader interfaces.
uint64_t capacity_;//流容量
bool error_ {};
//新增:
std::string buffer_ {};//流缓存
uint64_t bytes_pushed_ {};//写入字节
uint64_t bytes_poped_ {};//读取字节
bool isclosed_ {};//判断关闭
std::string buffer_ {};
可视为整个字节流的写入以及读取的队列uint64_t bytes_pushed_ {};
用于统计总写入字节数uint64_t bytes_poped_ {};
用于统计总读取字节数bool isclosed_ {};
控制流的关闭
byte_stream.cc
实验要求我们实现下列函数:
C++
bool Writer::is_closed() const
void Writer::push( string data )
void Writer::close()
uint64_t Writer::available_capacity() const
uint64_t Writer::bytes_pushed() const
bool Reader::is_finished() const
uint64_t Reader::bytes_popped() const
string_view Reader::peek() const
void Reader::pop( uint64_t len )
uint64_t Reader::bytes_buffered() const
- 首先在初始化时初始化流缓存的大小
C++
ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity )
{
buffer_.reserve( capacity );
}
bool Writer::is_closed() const
返回isclosed_
即可
*
void Writer::push( string data )
首先判断流是否关闭is_closed()
,如关闭则return
后赋len
值为字节流大小data.size()
与可用容量available_capacity()
的最小值(防止越界)
给buffer_
加入特定长度的data
给bytes_pushed_
累加len
*
void Writer::close()
给isclosed_
赋值为true
*
uint64_t Writer::available_capacity() const
返回capacity_-buffer_.size()
*
uint64_t Writer::bytes_pushed() const
返回bytes_pushed_
*
bool Reader::is_finished() const
当Writer
写入关闭且全部读取完后才会关闭Reader
return isclosed_ && buffer_.size() == 0
*
uint64_t Reader::bytes_popped() const
返回bytes_poped_
*
string_view Reader::peek() const
构建string_view sv
范围为buffer_
的有效长度的字符串
string_view sv( buffer_.data(), min( static_cast<uint64_t>(1024), bytes_buffered() ) );
static_cast<uint64_t>(1024)
指的是不超过这个指标
*
void Reader::pop( uint64_t len )
对buffer_
进行弹出操作buffer_.erase(buffer_.begin(), buffer_.begin() + len )
再对bytes_poped_
累加len
*
uint64_t Reader::bytes_buffered() const
返回buffer_.size()
则总代码src/byte_stream.cc
为:
C++
#include "byte_stream.hh"
using namespace std;
ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity )
{
buffer_.reserve( capacity );
}
bool Writer::is_closed() const
{
// Your code here.
return isclosed_;
}
void Writer::push( string data )
{
if ( is_closed() )
return;
// Your code here.
uint64_t len = min( data.size(), available_capacity() );
buffer_ += data.substr( 0, len );
bytes_pushed_ += len;
}
void Writer::close()
{
// Your code here.
isclosed_ = true;
}
uint64_t Writer::available_capacity() const
{
// Your code here.
return capacity_ - buffer_.size();
}
uint64_t Writer::bytes_pushed() const
{
// Your code here.
return bytes_pushed_;
}
bool Reader::is_finished() const
{
// Your code here.
return isclosed_ && buffer_.size() == 0;
}
uint64_t Reader::bytes_popped() const
{
// Your code here.
return bytes_poped_;
}
string_view Reader::peek() const
{
// Your code here.
string_view sv( buffer_.data(), min( static_cast<uint64_t>( 1024 ), bytes_buffered() ) );
return sv;
}
void Reader::pop( uint64_t len )
{ // Your code here.
buffer_.erase( buffer_.begin(), buffer_.begin() + len );
bytes_poped_ += len;
}
uint64_t Reader::bytes_buffered() const
{
// Your code here.
return buffer_.size();
}
测试指令: cmake --build build --target check0