KPBOT / CS144 Lab0 笔记 — 从应用层到字节流

Created Mon, 20 Jul 2026 00:00:00 +0000 Modified Mon, 20 Jul 2026 00:00:00 +0000

背景

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,都围绕这个字节流模型展开。


References: CSDIY, CS144