其实我很早就已经写好了CS144的lab0,今天本应该写Lab2的。但是发现自己写了就已经快忘了…没办法,必须记录一下!由于目前没有具体学习TCP,写起来也是很煎熬。主要思路来源于这位大佬博客里的代码 https://lrl52.top/。我的文章主要涉及代码部分,具体配置可以参照官方给的pdf~~
webget
作为热身项目难度不是很大。课程已经给你封装了TCPSocket类,你根据语法使用即可。文章目前只关注项目实现,对框架细节等方面因为能力问题先不细究。(等稍微厉害一点再回来学!)直接上代码:
#include "socket.hh"
include "util.hh"
include <cstdlib>
include <iostream>
using namespace std;
void get_URL(const string &host, const string &path) {
// Your code here.
// You will need to connect to the "http" service on
// the computer whose name is in the "host" string,
// then request the URL path given in the "path" string.
// Then you'll need to print out everything the server sends back,
// (not just one call to read() -- everything) until you reach
// the "eof" (end of file).
TCPSocket sock;
sock.connect(Address(host, "http"));
sock.write("GET " + path + " HTTP/1.1\r\n");
sock.write("Host: " + host + "\r\n");
sock.write("Connection: close\r\n");
sock.write("\r\n");
while(!sock.eof()){
cout << sock.read();
}
sock.close();
}
TCPSocket sock 用于创造一个TCP协议栈。
Address(host, "http") 将主机名(如 "www.example.com")和服务名 "http"(默认端口 80)解析为 IP 地址和端口。connect() 向该服务器发起 TCP 三次握手,建立可靠的双向字节流连接。
接下来三行构建了请求行:
GET /index.html HTTP/1.1
Host: cs144.keithw.org
Connection: close
注意:\r\n:CR+LF,HTTP 协议规定每行必须以回车换行结束。
connection: close 的作用在于告诉服务器:我传输完毕了!请你主动关闭TCP连接。
服务器接收后调用close( )来关闭它这端的socket,并且发送一个FIN (finish)包。
什么是FIN包呢?这在后面的Lab中也有涉及。
FIN 包是 TCP 正常关闭连接时,一方发出的“我没有数据要发了”的信号包。它让对端知道可以准备读取完剩余数据后关闭连接,并且是系统中 read() 返回 0(EOF)以及 sock.eof() 检测到连接结束的根本原因。
在TCP连接关闭时,FIN包的使用如下:
- 第一次挥手:客户端发送FIN包,表示它的数据发送完毕,进入FIN_WAIT_1状态。
- 第二次挥手:服务器收到FIN包后,回复一个ACK包,确认收到关闭请求,进入CLOSE_WAIT状态。
- 第三次挥手:服务器发送自己的FIN包,表示它的数据也发送完毕,进入LAST_ACK状态。
- 第四次挥手:客户端收到服务器的FIN包后,回复ACK包,进入TIME_WAIT状态,等待一段时间后关闭连接。
在 webget 实验中,由于 Connection: close 的约定,服务器成为主动关闭方,顺序与上述描述正好相反。
当客户端收到服务器的 FIN 后,sock.eof() 返回 true,循环终止。此时调用 sock.close() 会向服务器发送 FIN,完成连接的完全关闭,并释放本地 socket 资源。
ByteStream
这里的ByteStream仅仅是一个简单的FIFO (First In First Out)序列,充当一个数据缓冲区的作用,给应用层提供一个可靠的数据接口,来实现可靠,有序的字节流服务。上代码:
#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
define SPONGE_LIBSPONGE_BYTE_STREAM_HH
include <string>
include <vector>
//! \brief An in-order byte stream.
//! Bytes are written on the "input" side and read from the "output"
//! side. The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
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::vector<char> _buffer;
size_t _capacity;
size_t _written_cnt;
size_t _read_cnt;
int _head, _tail;
bool _input_ended_flag = false;
bool _error = false;
public:
//! Construct a stream with room for capacity bytes.
ByteStream(const size_t capacity);
//! \name "Input" interface for the writer
//!@{
//! Write a string of bytes into the stream. Write as many
//! as will fit, and return how many were written.
//! \returns the number of bytes accepted into the stream
size_t write(const std::string &data);
//! \returns the number of additional bytes that the stream has space for
size_t remaining_capacity() const;
//! Signal that the byte stream has reached its ending
void end_input();
//! Indicate that the stream suffered an error.
void set_error() { _error = true; }
//!@}
//! \name "Output" interface for the reader
//!@{
//! Peek at next "len" bytes of the stream
//! \returns a string
std::string peek_output(const size_t len) const;
//! Remove bytes from the buffer
void pop_output(const size_t len);
//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \returns a string
std::string read(const size_t len);
//! \returns true if the stream input has ended
bool input_ended() const;
//! \returns true if the stream has suffered an error
bool error() const { return _error; }
//! \returns the maximum amount that can currently be read from the stream
size_t buffer_size() const;
//! \returns true if the buffer is empty
bool buffer_empty() const;
//! \returns true if the output has reached the ending
bool eof() const;
//!@}
//! \name General accounting
//!@{
//! Total number of bytes written
size_t bytes_written() const;
//! Total number of bytes popped
size_t bytes_read() const;
//!@}
};
endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH
注意,这里使用 std::vector<char> 来定义了缓冲区 _buffer,_capacity为_buffer的容量,_written_cnt是已经写入的字节总数,_read_cnt 是已经读取的字节总数,_head , _tail 分别指向下一个读取位置,下一个写入位置,初始状态_input_ended_flag, _error均为false。
include "byte_stream.hh"
// Dummy implementation of a flow-controlled in-memory byte stream.
// For Lab 0, please replace with a real implementation that passes the
// automated checks run by make check_lab0.
// You will need to add private members to the class declaration in byte_stream.hh
template <typename... Targs>
void DUMMY_CODE(Targs &&... / unused /) {}
using namespace std;
ByteStream::ByteStream(const size_t capacity):
_buffer(capacity + 1), _capacity(capacity), _written_cnt(0),
_read_cnt(0), _head(0), _tail(_capacity) {}
size_t ByteStream::write(const string &data) {
auto ret = min(data.size(), remaining_capacity());
for(size_t i = 0; i < ret; ++i){
_tail = (_tail + 1) % (_capacity + 1);
_buffer[_tail] = data[i];
}
_written_cnt += ret;
return ret;
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
string ret;
auto n = min(len, buffer_size());
for(size_t i = 0; i < n; ++i){
ret.push_back(_buffer[(_head + i) % (_capacity + 1)]);
}
return ret;
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
auto n = min(len, buffer_size());
_head = (_head + n) % (_capacity + 1);
_read_cnt += n;
}
//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \param[in] len bytes will be popped and returned
//! \returns a string
std::string ByteStream::read(const size_t len) {
auto ret = peek_output(len);
pop_output(len);
return ret;
}
void ByteStream::end_input() { _input_ended_flag = true; }
bool ByteStream::input_ended() const { return _input_ended_flag; }
size_t ByteStream::buffer_size() const { return _written_cnt - _read_cnt; }
bool ByteStream::buffer_empty() const {
if (buffer_size() == 0) { return true; }
return false;
}
bool ByteStream::eof() const { return _input_ended_flag && buffer_empty(); }
size_t ByteStream::bytes_written() const { return _written_cnt; }
size_t ByteStream::bytes_read() const { return _read_cnt; }
size_t ByteStream::remaining_capacity() const {return _capacity - buffer_size(); }
_buffer开始定义了capacity + 1,这行代码使用了 std::vector 的构造函数。std::vector 提供了多个构造函数,其中之一是 explicit vector(size_type count); (注意这里必须显式构造) 。此处定义了一个和capacity + 1长度的std::vector<char>对象,并且对象数组内均初始化为'\0'。
这个ByteStream的原理是利用一个环形缓冲区。注意每次写入到末尾时,都会通过这行代码从末尾返回到头部
_tail = (_tail + 1) % (_capacity + 1);
read 和 write 函数分别用来读取和写入代码,peek_output 仅仅返回指定长度的数据,并不真实读取。pop_output 函数则弹出指定长度的数据。两者结合可写出read。
评论(0)
暂无评论