背景
CS144 是斯坦福的计算机网络课程。要用 C++ 手撸 TCP/IP 协议栈,涉及大量数据结构,对本科生有一定挑战——最大的挑战其实是全英文文档。
我对计算机网络的理解一直很碎片,概念都知道,但串不起来。趁这个机会系统整理一下。
网络分层回顾
分层标准很多,这里对比 OSI 七层和 TCP/IP 四层。
OSI 七层模型:
7. 应用层 — 为用户提供网络服务
6. 表示层 — 编解码、加解密、压缩
5. 会话层 — 管理应用进程间会话
4. 传输层 — 端到端数据传输(TCP/UDP)
3. 网络层 — 路由与寻址(IP)
2. 数据链路层 — 帧编码与差错控制
1. 物理层 — 网线、光纤等物理介质
实际网络中,5、6 层通常合并到应用层。TCP/IP 更务实——加密(OSI 表示层)和会话管理(OSI 会话层)直接由应用协议自己搞定。
为什么要分层? 每层只对本层负责,调用下层、服务上层,同层之间通过 Protocol 通信。
任务一:telnet 和 netcat
telnet
应用层协议,基于 TCP,用于远程登录。无加密、明文传输,已被 SSH 取代。
连接过程:
客户端 服务端
|-- 连接 23 端口 -->|
|<-- 欢迎信息 ------|
|-- 用户名 --------->|
|<-- 请求密码 ------|
|-- 密码 ----------->|
|<-- 登录成功 ------|
远程会话开始
Lab0 主要用它测试网络服务——通过 telnet 发 HTTP GET 请求。课程网站已下线,用 Flask 本地启了个路由凑合:
import flask
app = flask.Flask(__name__)
@app.route("/hello")
def hello():
return "Hello World!"
@app.route('/hasher/xyzzy')
def hasher():
return 'QWx0NhMPkoM/bJr/ohvHXlviFhOyYrYb+qqdOnwLYo4\n'
app.run(host='0.0.0.0', port=80)
netcat
netcat 监听端口,telnet 连过去看连接状态。细节略。
这些操作走的都是 TCP,但为什么没观察到三次握手、四次挥手?因为它们发生在 TCP 协议栈内部,后续 Lab 会在代码层面看到具体实现。
任务二:webget
把 telnet 做的事转成 C++ 代码——通过 TCP socket 建立连接,发 GET 请求,打印响应。
void get_URL(const string &host, const string &path) {
TCPSocket sock{};
sock.connect(Address(host, "http"));
sock.write("GET " + path + " HTTP/1.1\r\nHost: " + host + "\r\n\r\n");
sock.shutdown(SHUT_WR);
while (!sock.eof()) {
cout << sock.read();
}
sock.close();
}
课程网站挂了,翻 Makefile 定位到 test 的实际执行指令,改地址指向本地服务,pass。
这里理解了各层职责分明:
- HTTP → 我要获取网页
- TCP → 保证数据完整、有序到达
- IP → 负责把数据包送到目标机器
任务三:ByteStream(有序字节流)
实现一个带容量上限的有序字节流——写端到缓冲区到读端,写满阻塞。peek 读不删,pop 读且删,end_input 标记结束,eof = buffer_empty && input_ended。
核心实现:
ByteStream::ByteStream(const size_t capacity) : _capacity(capacity) {}
size_t ByteStream::write(const string &data) {
size_t len = data.size();
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;
}
string ByteStream::peek_output(const size_t len) const {
size_t length = min(len, _buffer.size());
return string().assign(_buffer.begin(), _buffer.begin() + length);
}
void ByteStream::pop_output(const size_t len) {
size_t length = min(len, _buffer.size());
_read_count += length;
while (length--) _buffer.pop_front();
}
私有成员:
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;
涉及基础数据结构,快速通过。
总结
CS144 Lab0 的目标不是实现复杂协议,而是从应用层理解 TCP 的服务模型,熟悉后续要用到的数据结构。
TCP 提供的抽象:可靠字节流
应用程序
|
可靠字节流 (ByteStream)
|
TCP
|
不可靠 IP 数据报
|
网络
TCP 隐藏了底层的不可靠性:
| 底层问题 | TCP 保证 |
|---|---|
| IP 包可能丢失 | 可靠传输 |
| 包可能乱序 | 按序交付 |
| 包可能重复 | 去重 |
| 包大小有限 | 分段重组 |
ByteStream 是整个 TCP 实现的基础
TCP 不传输「消息」,而是维护一个连续的字节编号空间:
应用发送: "hello world"
TCP 看到:
byte0 h
byte1 e
byte2 l
byte3 l
byte4 o
...
后续 Lab1 的 StreamReassembler、Lab2 的 TCPReceiver,都围绕这个字节流模型展开。