在虚拟机上运行 telnet cs144.keithw.org http
,这句话的意思是让 telnet
程序在你的计算机和另外一个计算机(名字叫 cs144.keithw.org
)之间建立一个可靠的字节流连接,并且该计算机上运行着特定的服务:http
(Hyper-Text Transfer Protocol 超文本传输服务)
键入 GET /hello HTTP/1.1
,这句话告诉服务器 URL 的路径(path)
键入 Host: cs144.keithw.org
这告诉服务器 URL 的主机(host)
然后再敲一次回车,则这个 HTTP 请求就会被发出。
效果如下:
这等价于获取网页 http://cs144.keithw.org/hello 的信息
现在我们已经知道 telnet
可以作为客户端程序,现在可以试一下服务器端:可以等待用户连接。
运行:netcat -v -l -p 9090
( netcat
是一个能够读写TCP、UDP连接的工具, -v
是 verbose ,显示详细信息,-l
表示 listen,是在监听是否有客户端连接,-p
指的是监听端口)
效果如下:
打开另一个终端,运行 telnet localhost 9090
下面来写一个程序来读一个网页,用的是操作系统提供的 stream socket ,这个 socket 看起来就像一个普通的文件描述子(和磁盘上的文件相似,也和 stdin,stdout
相似)当两个 stream sockets 连接起来时,写到其中一个 socket 中的字节会以相同的顺序从另一个 socket 中出来。
但是现实里因特网并不提供一个可靠的字节传输,Internet 做的事情叫做尽最大可能(best effort)地向目的地交付短数据,叫做 Internet datagrams 。每个数据报文都包含一些元信息(headers),指定了源地址和目标地址,以及一些净荷载(payload)(最大约为 1500 字节)。
尽管网络尽可能地去发所有地报文,但是这些报文会:
通常是由源和目标处地计算机操作系统来将这些“尽可能”交付地报文转化为“可靠的字节流传输”。
两个计算机必须共同协作来确保流中的每个字节都最终能够按照顺序交付。并且他们要告诉彼此自己准备接收的数据大小,这些是通过 1981 年建立的 Transmission Control Protocol (TCP)来实现的。
首先 git clone https://github.com/cs144/sponge
,假如慢的话可以先把仓库移到 gitee.com 上,然后再 clone。注意 cs144 提供的虚拟机是可以通过 ssh -p 2222 cs144@localhost
从外面连上的,配合 xshell 这类的工具,用起来不要太爽hhh
然后是一系列的命令:
作业主要用的是现代的 C++ 风格,参考 http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
其基础思想是使每一个对象都有尽可能少的公有接口(public interface),其内部有很多安全检查,使得很难被误用,并且能够自己释放资源(而不是需要成对的 malloc/free
,new/delete
)
这种风格叫做:“Resource acquisition is initialization” (RAII,资源获取即为初始化 )
https://cs144.github.io/doc/lab0
其中 Socket
是一种 FileDescriptor
,TCPSocket
是一种 Socket
这个类是一个 reference-count handle to a file descriptor
继承关系:
这是网络 socket(TCP,UDP,…) 的基类,继承关系如下:
是 TCP socket 的一个 wrapper ,继承关系如下:
Wrapper around IPv4 addresses and DNS operations.
Inside libsponge/util
:
file_descriptor.hh
#ifndef SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#define SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#include "buffer.hh"
#include
#include
#include
#include
class FileDescriptor {
class FDWrapper {
public:
int _fd;
bool _eof = false;
bool _closed = false;
unsigned _read_count = 0;
unsigned _write_count = 0;
explicit FDWrapper(const int fd);
~FDWrapper();
void close();
FDWrapper(const FDWrapper &other) = delete;
FDWrapper &operator=(const FDWrapper &other) = delete;
FDWrapper(FDWrapper &&other) = delete;
FDWrapper &operator=(FDWrapper &&other) = delete;
};
std::shared_ptr<FDWrapper> _internal_fd;
// private constructor used to duplicate the FileDescriptor (increase the reference count)
explicit FileDescriptor(std::shared_ptr<FDWrapper> other_shared_ptr);
protected:
void register_read() { ++_internal_fd->_read_count; }
void register_write() { ++_internal_fd->_write_count; }
public:
explicit FileDescriptor(const int fd);
~FileDescriptor() = default;
std::string read(const size_t limit = std::numeric_limits<size_t>::max());
void read(std::string &str, const size_t limit = std::numeric_limits<size_t>::max());
size_t write(const char *str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
size_t write(const std::string &str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
size_t write(BufferViewList buffer, const bool write_all = true);
void close() { _internal_fd->close(); }
FileDescriptor duplicate() const;
void set_blocking(const bool blocking_state);
int fd_num() const { return _internal_fd->_fd; }
bool eof() const { return _internal_fd->_eof; }
bool closed() const { return _internal_fd->_closed; }
unsigned int read_count() const { return _internal_fd->_read_count; }
unsigned int write_count() const { return _internal_fd->_write_count; }
FileDescriptor(const FileDescriptor &other) = delete;
FileDescriptor &operator=(const FileDescriptor &other) = delete;
FileDescriptor(FileDescriptor &&other) = default;
FileDescriptor &operator=(FileDescriptor &&other) = default;
};
#endif // SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
socket.hh
#ifndef SPONGE_LIBSPONGE_SOCKET_HH
#define SPONGE_LIBSPONGE_SOCKET_HH
#include "address.hh"
#include "file_descriptor.hh"
#include
#include
#include
#include
class Socket : public FileDescriptor {
private:
Address get_address(const std::string &name_of_function,
const std::function<int(int, sockaddr *, socklen_t *)> &function) const;
protected:
Socket(const int domain, const int type);
Socket(FileDescriptor &&fd, const int domain, const int type);
template <typename option_type>
void setsockopt(const int level, const int option, const option_type &option_value);
public:
void bind(const Address &address);
void connect(const Address &address);
void shutdown(const int how);
Address local_address() const;
Address peer_address() const;
void set_reuseaddr();
};
class UDPSocket : public Socket {
protected:
explicit UDPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_DGRAM) {}
public:
UDPSocket() : Socket(AF_INET, SOCK_DGRAM) {}
struct received_datagram {
Address source_address;
std::string payload;
};
received_datagram recv(const size_t mtu = 65536);
void recv(received_datagram &datagram, const size_t mtu = 65536);
void sendto(const Address &destination, const BufferViewList &payload);
void send(const BufferViewList &payload);
};
class TCPSocket : public Socket {
private:
explicit TCPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_STREAM) {}
public:
TCPSocket() : Socket(AF_INET, SOCK_STREAM) {}
void listen(const int backlog = 16);
TCPSocket accept();
};
class LocalStreamSocket : public Socket {
public:
explicit LocalStreamSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_UNIX, SOCK_STREAM) {}
};
#endif // SPONGE_LIBSPONGE_SOCKET_HH
address.hh
#ifndef SPONGE_LIBSPONGE_ADDRESS_HH
#define SPONGE_LIBSPONGE_ADDRESS_HH
#include
#include
#include
#include
#include
#include
#include
class Address {
public:
class Raw {
public:
sockaddr_storage storage{};
operator sockaddr *();
operator const sockaddr *() const;
};
private:
socklen_t _size;
Raw _address{};
Address(const std::string &node, const std::string &service, const addrinfo &hints);
public:
Address(const std::string &hostname, const std::string &service);
Address(const std::string &ip, const std::uint16_t port);
Address(const sockaddr *addr, const std::size_t size);
bool operator==(const Address &other) const;
bool operator!=(const Address &other) const { return not operator==(other); }
std::pair<std::string, uint16_t> ip_port() const;
std::string ip() const { return ip_port().first; }
uint16_t port() const { return ip_port().second; }
uint32_t ipv4_numeric() const;
std::string to_string() const;
socklen_t size() const { return _size; }
operator const sockaddr *() const { return _address; }
};
#endif // SPONGE_LIBSPONGE_ADDRESS_HH
填空的代码如下:
TCPSocket sock1{};
sock1.connect(Address(host, "http"));
sock1.write("GET "+path+" HTTP/1.1\r\nHost: "+host+"\r\n\r\n");
sock1.shutdown(SHUT_WR);
while(!sock1.eof()){
auto recvd = sock1.read();
cout << recvd ;
}
这部分的实现我出现的问题是 EOF 的判定,相关初始化以及改变以及整体代码如下:
// byte_stream.hh
#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include
#include
#include
#include
#include
#include
//! \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.
size_t _capacity;
size_t _used;
std::string _data;
bool _input_ended;
size_t _byte_written;
size_t _byte_read;
bool _eof;
bool _error{}; //!< Flag indicating that the stream suffered an error.
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 vector of bytes read
std::string read(const size_t len) {
const auto ret = peek_output(len);
pop_output(len);
return ret;
}
//! \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
// byte_stream.cc
#include "byte_stream.hh"
#include
#include
#include
// 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):
_capacity(capacity),_used(0),_data(""),
_input_ended(false),_byte_written(0),
_byte_read(0),_eof(false){}// DUMMY_CODE(capacity); }
size_t ByteStream::write(const string &data) {
// DUMMY_CODE(data);
size_t r_capacity = remaining_capacity();
if(r_capacity == 0)
return 0;
size_t write_size = min(data.length(), r_capacity);
_data.append(data, 0, write_size);
_byte_written += write_size;
_used += write_size;
return write_size;
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
size_t true_len = min(len, _used);
return _data.substr(0, true_len);
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
size_t true_len = min(len, _used);
_data = _data.substr(true_len, _used - true_len);
_used -= true_len;
_byte_read += true_len;
if(buffer_empty() && input_ended())
_eof = true;
}
void ByteStream::end_input() {
_input_ended = true;
if(buffer_empty())
_eof = true;
}
bool ByteStream::input_ended() const { return _input_ended; }
size_t ByteStream::buffer_size() const { return _used; }
bool ByteStream::buffer_empty() const { return _used == 0; }
bool ByteStream::eof() const { return _eof; }
size_t ByteStream::bytes_written() const { return _byte_written; }
size_t ByteStream::bytes_read() const { return _byte_read; }
size_t ByteStream::remaining_capacity() const { return _capacity - _used; }
但是有一个 test 是超时的… 我怀疑可能是测试抓取了一个墙外的网站吧。。