Computer Networking 课程实验。

  • Lab 0: networking warmup

  • Lab 1: stitching substrings into a byte stream

  • Lab 2: the TCP receiver

  • Lab 3: the TCP sender

  • Lab 4: the TCP connection

  • Lab 5: the network interface

  • Lab 6: the IP router

Lab 0: networking warmup

Lab0 主要两个任务。其一是个简单的应用层程序,实现简单的网页内容获取。直接调用 Linux 的 TCP 协议栈接口 ,会在之后完成 lab4 时,使用自己写的 TCP 协议替换系统实现。其二是实现一个字节流读写程序。

1 Set up GNU/Linux on your computer

安装课程自带的Linux镜像 [cs144](https://stanford.edu/class/cs144/vm howto/vm-howto-image.html) 或配置自己的Linux环境

账户:cs144@localhost

密码:lttt1998

2 Networking by hand

2.1 Fetch a Web page

telnet cs144.keithw.org http or telnet cs144.keithw.org 80

让telnet程序在你的计算机与另一台计算机(cs144.keithw.org)之间建立可靠的的字节流,并运行一个特定的服务:HTTP(Hyper-Text Transfer Protocol 超文本传输服务)

Telnet 是 TCP/IP 协议簇中的一个虚拟终端协议,它允许连接到远程主机。 Telnet 运行在 OSI 参考模 型的应用层,它利用 TCP 来保证正确和有序的在客户机和服务器之间传输数据。

1
2
GET /hello HTTP/1.1
Host: cs144.keithw.org

等价于获取网页http://cs144/keithw.org/hello的信息。

2.2 Send yourself an email

telnet 148.163.153.234 smtp

SMTP( Simple Mail Transfer Protocol),简单邮件传送协议。

1
2
3
4
5
6
7
8
9
10
11
12
HELO mycomputer.stanford.edu //等待回应"250 2.1.0 Sender ok"
MAIL FROM: sunetid@stanford.edu //告知Sender,等待回应"250 2.1.0 Sender ok"
RCPT TO: sunetid@stanford.edu //告知Recipient,等待回应"250 2.1.5 Recipient ok"
DATA //准备开始
//header
From: sunetid@stanford.edu
To: sunetid@stanford.edu
Subject: Hello from CS144 Lab 0!

//body
.
QUIT

2.3 Listening and connecting

Telnet 既可以作为客户端程序,也可以作为服务器端:可以等待用户连接。

netcat -v -l -p 9090

netcat 是一个能够读写TCP、UDP连接的工具, -v 是 verbose ,显示详细信息,-l 表示 listen,是在监听是否有客户端连接,-p 指的是监听端口)

在另一终端运行telnet localhost 9090

3 Writing a network program using an OS stream socket

下面来写一个程序来读一个网页,用的是操作系统提供的 stream socket ,这个 socket 看起来就像一个普通的文件描述子(和磁盘上的文件相似,也和 stdin,stdout 相似)当两个 stream sockets 连接起来时,写到其中一个 socket 中的字节会以相同的顺序从另一个 socket 中出来。

但是现实里因特网并不提供一个可靠的字节传输,Internet 做的事情叫做尽最大可能(best effort)地向目的地交付短数据,叫做 Internet datagrams 。每个数据报文都包含一些元信息(headers),指定了源地址和目标地址,以及一些净荷载(payload data)(最大约为 1500 字节)。

尽管网络尽可能地去发所有地报文,但是这些报文会:

  1. 丢失
  2. 失序
  3. 出错
  4. 重复

通常是由源和目标处的计算机操作系统来将这些“尽可能”交付地报文转化为“可靠的字节流传输”。

两个计算机必须共同协作来确保流中的每个字节都最终能够按照顺序交付。并且他们要告诉彼此自己准备接收的数据大小,这些是通过 1981 年建立的 Transmission Control Protocol (TCP)来实现的。


Socket 是一种 FileDescriptorTCPSocket 是一种 Socket

FileDescriptor

这个类是一个 reference-count handle to a file descriptor。

继承关系:

lab0_1

file_descriptor.hh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#ifndef SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#define SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH

#include "buffer.hh"

#include <array>
#include <cstddef>
#include <limits>
#include <memory>

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

这是网络 socket(TCP,UDP,…) 的基类,继承关系如下:

lab0_2

socket.hh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#ifndef SPONGE_LIBSPONGE_SOCKET_HH
#define SPONGE_LIBSPONGE_SOCKET_HH

#include "address.hh"
#include "file_descriptor.hh"

#include <cstdint>
#include <functional>
#include <string>
#include <sys/socket.h>

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

TCPSocket

是 TCP socket 的一个 wrapper ,继承关系如下:

在这里插入图片描述

Address

Wrapper around IPv4 addresses and DNS operations.

address.hh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#ifndef SPONGE_LIBSPONGE_ADDRESS_HH
#define SPONGE_LIBSPONGE_ADDRESS_HH

#include <cstddef>
#include <cstdint>
#include <netdb.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <utility>

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

Writing webget

  • SHUT_RD/WR/RDWR,先用SHUT_WR关闭写,避免服务器等待
1
2
3
4
5
6
7
8
9
10
11
12
13
void get_URL(const string &host, const string &path) {
TCPSocket sock{};
sock.connect(Address(host,"http"));
string input("GET "+path+" HTTP/1.1\r\nHost: "+host+"\r\n\r\n");
sock.write(input);
// If you don’t shut down your outgoing byte stream,
// the server will wait around for a while for you to send
// additional requests and won’t end its outgoing byte stream either.
sock.shutdown(SHUT_WR);
while(!sock.eof())
cout<<sock.read();
sock.close();
}

4 An in-memory reliable byte stream

要求实现一个有序字节流类(in-order byte stream),使之支持读写、容量控制。这个字节流类似于一个带容量的队列,从一头读,从另一头写。当流中的数据达到容量上限时,便无法再写入新的数据。特别的,写操作被分为了peek和pop两步。peek为从头部开始读取指定数量的字节,pop为弹出指定数量的字节。

byte_stream.hh

1
2
3
4
5
6
7
8
9
10
Copyclass ByteStream {
private:
// Your code here -- add private members as necessary.
std::deque<char> _buffer = {};
size_t _capacity = 0;
size_t _read_count = 0;
size_t _write_count = 0;
bool _input_ended_flag = false;
bool _error = false; //!< Flag indicating that the stream suffered an error.
//......

byte_stream.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
CopyByteStream::ByteStream(const size_t capacity) : _capacity(capacity) {}

size_t ByteStream::write(const string &data) {
size_t len = data.length();
if (len > _capacity - _buffer.size()) {
len = _capacity - _buffer.size();
}
_write_count += len;
for (size_t i = 0; i < len; i++) {
_buffer.push_back(data[i]);
}
return len;
}

//! \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 length = len;
if (length > _buffer.size()) {
length = _buffer.size();
}
return string().assign(_buffer.begin(), _buffer.begin() + length);
}

//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) {
size_t length = len;
if (length > _buffer.size()) {
length = _buffer.size();
}
_read_count += length;
while (length--) {
_buffer.pop_front();
}
return;
}

void ByteStream::end_input() { _input_ended_flag = true; }

bool ByteStream::input_ended() const { return _input_ended_flag; }

size_t ByteStream::buffer_size() const { return _buffer.size(); }

bool ByteStream::buffer_empty() const { return _buffer.size() == 0; }

bool ByteStream::eof() const { return buffer_empty() && input_ended(); }

size_t ByteStream::bytes_written() const { return _write_count; }

size_t ByteStream::bytes_read() const { return _read_count; }

size_t ByteStream::remaining_capacity() const { return _capacity - _buffer.size(); }

Lab 1: stitching substrings into a byte stream

Lab 2: the TCP receiver

Lab 3: the TCP sender

Lab 4: the TCP connection

Lab 5: the network interface

Lab 6: the IP router