0%
boxmoe_header_banner_img

加载中

CS144 Sponge Lab0


avatar
akurisu 2026年4月11日 2026年4月11日 108

其实我很早就已经写好了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\nCR+LF,HTTP 协议规定每行必须以回车换行结束。

connection: close 的作用在于告诉服务器:我传输完毕了!请你主动关闭TCP连接。

服务器接收后调用close( )来关闭它这端的socket,并且发送一个FIN (finish)包。

什么是FIN包呢?这在后面的Lab中也有涉及。
FIN 包是 TCP 正常关闭连接时,一方发出的“我没有数据要发了”的信号包
。它让对端知道可以准备读取完剩余数据后关闭连接,并且是系统中 read() 返回 0(EOF)以及 sock.eof() 检测到连接结束的根本原因。

在TCP连接关闭时,FIN包的使用如下:

  1. 第一次挥手:客户端发送FIN包,表示它的数据发送完毕,进入FIN_WAIT_1状态。
  2. 第二次挥手:服务器收到FIN包后,回复一个ACK包,确认收到关闭请求,进入CLOSE_WAIT状态。
  3. 第三次挥手:服务器发送自己的FIN包,表示它的数据也发送完毕,进入LAST_ACK状态。
  4. 第四次挥手:客户端收到服务器的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)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码
后退
前进
刷新
复制
粘贴
全选
删除
返回首页
0%
目录
顶部
底部
📖 文章导读