- 1 建立课程开发环境
- 2 Fetch a Web page
- 2.1 浏览器发送http请求
- 2.2 telnet模拟浏览器发送http请求
- 3 WebGet
- 4 An in-memory reliable byte stream(缓冲区队列)
课程使用的是基于Linux的系统,可以使用ubuntu发行版。(安装过程略过)
可参考:https://blog.csdn.net/u_hcy2000/article/details/121506905?spm=1001.2014.3001.5501
使用telnet来体会浏览器是如何通过HTTP协议从服务器获取一个页面(HTML文档)的
2.1 浏览器发送http请求- 通过浏览器发送 http://cs144.keithw.org/hello 请求
- 查看页面响应结果:Hello, CS144!
键入 telnet cs144.keithw.org http,http请求与服务器建立连接
键入 GET /hello HTTP/1.1 ,这句话告诉服务器 URL 的路径(path)
键入 Host: cs144.keithw.org 这告诉服务器 URL 的主机(host)
键入Connection: close 结束请求
键入 空行,完成了http请求
查看服务器的响应信息如下:
扩展:HTTP请求简介
设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。
HTTP 请求一共分为四个部分:请求行,请求头,空行,请求体。
HTTP响应报文格式:响应行、响应头、空行、响应体 四个部分。
使用TCPSocket来实现发送http请求,并获取响应信息,打印出来。
- 创建一个TCPSocket并与服务器建立连接。
- 向服务器发送请求,格式参照前面fetch a web page部分,注意在HTTP中每行的结尾应该为rn。
- 发送完请求后,客户端应该关闭TCPSocket的写功能,对应前面的Connection:close,告诉服务器请求已经发送完毕,服务器只要回复完数据后就可以立刻断开连接。
- 循环读取从服务器发送过来的信息,直到遇到EOF(end of file)。 最后记得需要关闭前面创建的TCPSocket。
课程代码:
void get_URL(const string &host, const string &path) {
// telnet cs144.keithw.org http
// GET /hello HTTP/1.1
// Host: cs144.keithw.org
// Connection: close
// 创建一个客户端对象
TCPSocket client_socket;
// 与服务器建立TCP连接
client_socket.connect(Address(host, "http"));
// 客户端发送请求request
string reqMsg = "GET "+path+" HTTP/1.1rn"+"Host: "+host+"rnConnection: closernrn";
client_socket.write(reqMsg);
// 客户端接收服务端的响应response
while(!client_socket.eof()){
string recvMsg = client_socket.read();
cout<
4 An in-memory reliable byte stream(缓冲区队列)
自定义一个字节流类型。
因特网本身并不提供对传输“可靠性”的保证,而是由客户端和服务器上的操作系统来完成这个任务。我们现在要实现的是位于TCP连接两端的套接字中的字节流数据结构。
ByteStream具有一定的容量,最大允许存储该容量大小的数据;在读取端读出一部分数据后,它会释放掉已经被读出的内容,以腾出空间继续让写端写入数据。
字节流以类ByteStream来实现,byte_stream.hh中声明了这个类,以及它的内部变量和成员函数;各个成员函数在 http://byte_stream.cc 中予以实现。
思路:
- 数据存储结构:队列
- 实现逻辑:注意输入端写数据,输出端输出数据两大主要操作
byte_stream.hh
// 输入端写入数据和输出端读取数据,满足先入先出的原则,可采用队列的数据结构实现缓冲区
// 注意区分,capacity代表的是缓冲区大小,buffer_size和_stream.size()代表缓冲区里实际内容的大小
class ByteStream {
private:
// Your code here -- add private members as necessary.
// Hint: This doesn't need to be a sophisticated data structure at
// all, but if any of your tests are taking longer than a second,
// that's a sign that you probably want to keep exploring
// different approaches.
// 创建一个缓冲区队列
std::deque _buf{};
// 缓冲区标准容量
size_t _capacity = 0;
// 输入端写入结束标志
bool _is_end_input = false;
// 输入端写入长度
size_t _bytes_written = 0;
// 输出端读取长度
size_t _bytes_read = 0;
bool _error{}; //!< Flag indicating that the stream suffered an error.
Byte_stream.cc
#include "byte_stream.hh"
template
void DUMMY_CODE(Targs &&... ) {}
using namespace std;
// 初始化字节流数据,设置缓冲区的最大容量
ByteStream::ByteStream(const size_t capacity) {
_capacity = capacity;
}
// 输入端写数据,存入到缓冲队列中
size_t ByteStream::write(const string &data) {
// 如果队列满了,返回0不能写入数据,写入为0
if(remaining_capacity() == 0) return 0;
// 将数据写入
size_t write_size = (data.size() <= remaining_capacity()) ? data.size(): remaining_capacity();
for(size_t i = 0; i < write_size; i++){
_buf.push_back(data[i]);
}
// 已经写入的数据长度更新
_bytes_written += write_size;
return write_size;
}
// 查看缓冲区的队列内容,但是不出队列
string ByteStream::peek_output(const size_t len) const {
size_t buf_size = (len <= buffer_size()) ? len : buffer_size();
string data;
data.assign(_buf.begin(),_buf.begin()+buf_size);
return data;
}
// 出队列,从缓冲区中删除len长度的数据
void ByteStream::pop_output(const size_t len) {
size_t buf_size = (len <= buffer_size()) ? len : buffer_size();
for(size_t i = 0; i < buf_size; i++){
_buf.pop_front();
}
_bytes_read += buf_size;
}
// 从缓冲区队列中读取数据
std::string ByteStream::read(const size_t len) {
string buf_read = peek_output(len);
pop_output(len);
return buf_read;
}
void ByteStream::end_input() {_is_end_input = true;}
bool ByteStream::input_ended() const { return _is_end_input; }
size_t ByteStream::buffer_size() const { return _buf.size(); }
bool ByteStream::buffer_empty() const { return _buf.size() == 0; }
bool ByteStream::eof() const { return input_ended() && buffer_empty(); }
size_t ByteStream::bytes_written() const { return _bytes_written; }
size_t ByteStream::bytes_read() const { return _bytes_read; }
size_t ByteStream::remaining_capacity() const { return _capacity - _buf.size(); }
总结:就是自定义一个字节缓冲区的数据结构,这里选择使用队列数据结构来存储数据,符合缓冲区的先进先出原则,且效率比较高效。



