FEATURED · 精选文章

从零手写TCP/IP协议栈:斯坦福CS144公开课实验指南

发布时间 / 2026/9/7 7:21:42
来源 / 创域科博编辑部
栏目 / 资讯中心
从零手写TCP/IP协议栈:斯坦福CS144公开课实验指南 如果你学计算机网络时总有种感觉概念都背过但数据包到底怎么经过网卡、协议栈、内核缓冲区最后变成对方收到的一段字节脑子里完全没有画面那这门斯坦福的公开课 CS144Introduction to Computer Networking值得认真过一遍。这门课最大的特点不是把 TCP 画成一张张 PPT而是让你用 C 从零实现一个属于自己的 TCP/IP 协议栈。做完之后你会亲手写的代码去连接真实互联网上的服务器真正“看到”数据包在网络中完整传输的过程。再看《计算机网络》教材时很多抽象概念会自动变成一张张可运行的流程图。这篇文章会把 CS144 的课程结构、实验内容、本地环境准备、编译运行、Wireshark 抓包验证、常见坑位和最佳实践一次讲清楚。无论你是准备校招面试、复习计算机网络还是单纯想自己写一遍 TCP 源码都可以直接按这篇文章的顺序走。1. 核心能力速览先给结论CS144 不是那种“看视频 做选择题”的课程而是一个强实践的工程项目核心产出是一套你自己写的 TCP 协议栈。能力项说明课程名称斯坦福大学 CS144Introduction to Computer Networking课程资源课程官网公开的实验手册、PPT、视频与模板代码核心功能从零实现 ByteStream、流重组、TCP 接收方、TCP 发送方、TCP 连接最终验证用自己实现的 TCP 连接课程提供的真实 HTTP 服务器并抓取网页编程语言C17硬件门槛普通笔记本即可无 GPU 要求操作系统Linux 推荐macOS、Windows WSL2 也可启动方式cmake/make 编译跑课程测试程序验证WebUI无纯命令行与测试框架API 接口不是服务型项目但实验会实现 HTTP Client 并连接真实服务批量任务课程自带的测试框架可一次性运行多组用例适合读者复习计算机网络、准备面试、想手写 TCP 协议栈的人整门课做完你会对这几个问题有非常直接的体感一个字节从应用层写到 socket中间要经过哪些缓冲区TCP 的序号为什么不是从 1 开始接收方收到乱序、重复、重叠的数据段时该怎么重组超时重传到底怎么控制完整的一次 HTTP 请求数据包在网络上是怎么一段一段流过去的下面按学习路径逐步展开。2. 适用场景与使用边界CS144 适合谁首先是准备秋招/春招的计算机专业学生。TCP 三次握手、四次挥手、滑动窗口、重传机制几乎是必考题但只背八股很容易被追问细节。亲手实现过一遍 TCP 发送方和接收方之后很多面试题会变成“你自己代码里怎么处理的”。其次是网络方向开发者。如果日常要跟 Socket、代理、物联网通信、局域网传输工具、抓包分析打交道这门课能帮你建立完整的协议栈视角。你能从系统层面去理解各种“数据包丢失”“连接断开”“乱序到达”问题而不只是会在 Wireshark 里看个大概。第三个场景是计网课程同步学习。很多人学谢希仁《计算机网络》或《计算机网络自顶向下方法》时看完 TCP 章节还是一头雾水。用 CS144 的实验去对照教材效果会比单刷教材好很多。使用边界也要说清楚课程需要一定的 C 基础。至少你要知道类、继承、STL 容器怎么用。零基础直接上来写 TCP容易同时被语言和协议两座山压住。实验是逐步搭建的不建议跳过前面的 Lab 直接写最后一个。课程实验会连接真实网络服务做抓包验证时请只在课程教学服务器、自己搭建的本地服务或明确有授权的目标上进行。不要在未授权的情况下抓取他人网络流量或扫描端口也不要拿课程代码去做任何破坏性、绕审或入侵类操作。网络传输涉及隐私与安全抓包时可能看到敏感数据。学习阶段尽量用本地服务和课程专用站点避免捕获真实业务流量。3. 环境准备与前置条件CS144 不挑显卡不挑显存真正要准备好的是一个 Linux 编译环境。推荐环境组合环境说明操作系统Ubuntu 22.04 / 24.04 最省心macOS 也可以Linux 环境Windows 用户建议装 WSL2原生支持性好编译器g 9 或更高版本要求支持 C17构建工具cmake、make版本管理git抓包工具Wireshark 或 tcpdump磁盘空间源码和编译产物很小预留几个 G 足够如果你是在 Ubuntu 或 WSL2 里直接用一行命令装齐基础依赖sudo apt update sudo apt install -y git g make cmake build-essential net-tools tcpdumpWireshark 可以用图形界面也可以只装命令行版sudo apt install -y wireshark安装 Wireshark 时如果系统询问是否允许非 root 用户抓包按自己需要选择。如果在纯命令行环境也可以用tshark做无界面抓包后面会讲到。macOS 用户可以用 Homebrewbrew install git cmake make tcpdump wireshark然后是获取课程实验代码。CS144 的模板代码是公开的课程官网上可以找到实验说明和代码仓库。拉下来之后放在一个你自己记得住、路径没有空格的目录里。mkdir -p ~/workspace/cs144 cd ~/workspace/cs144 git clone 课程模板仓库地址不同年份的课程版本对应的仓库地址可能不同以课程首页给出的链接为准。操作上凡是出现“按课程说明替换仓库地址”的地方都不要照抄死命令先看课程给的说明。再强调一次这个课程不依赖 GPU。从编译到跑完最后一个实验CPU 和内存都足够真正消耗的是你调试代码的时间。4. 实验结构与启动方式CS144 的实验是逐层递进的从最底层的内存字节流一路做到完整 TCP 连接。Lab 0先看到字节流实验 0 的核心目标是让你理解“一个可靠的字节流在内存里长什么样”。你需要实现一个ByteStream类一端写入字节另一端按顺序读取字节内部有缓冲区、容量限制、结束标志。这个类的应用场景就是后面 TCP 协议栈里用户态和内核态之间传递数据的基础。同时实验 0 会要求你用系统的 socket API 写一个webget小程序向课程指定的 HTTP 服务器发送 GET 请求然后把响应体打印出来。这一步算热身让你先看到“普通程序是怎么直接使用 TCP 的”。Lab 1把乱序数据拼接成有序流网络数据包到达接收方时不一定是按顺序的。可能后发的先到可能重复还可能重叠。实验 1 要求实现StreamReassembler接收一堆带索引的字节片段缓存乱序部分重复数据去重最终按正确的索引顺序向上层输出连续字节流。这里你会第一次真正接触“重组”这个概念。以后你在 Wireshark 里看到 TCP 乱序重传时就不会觉得是玄学了。Lab 2实现 TCP 接收方实验 2 要写TCPReceiver。它会接收 TCP segment解析其中的序号、负载、SYN/FIN 标志然后决定是否接受数据、应该回复多大的接收窗口、确认号是多少。这个实验里有个非常关键的点TCP 报文头里的序号和ByteStream的字节索引不是一回事。你要处理好 32 位回绕序号wraparound和 64 位绝对序号之间的转换。做完这个实验再看“TCP 序号为什么是相对的”“SYN 占一个序号吗”这类问题思路会非常清晰。Lab 3实现 TCP 发送方实验 3 要求实现TCPSender。发送方要维护发送队列、跟踪哪些字节已经被确认、哪些还在空中飞还要根据接收方通告的窗口大小决定能发多少数据并在超时后重传。这里你会自己写一套重传计时器。TCP 的超时重传、滑动窗口限制、快速重传等机制不是在教材里背的而是你亲手写在Timer逻辑里的。Lab 4串成完整 TCP 连接实验 4 把接收方和发送方组装成TCPConnection。你需要管理连接的完整状态机处理 SYN、ACK、FIN 这些标志处理正常收尾和异常断开。整个实验的高潮是用你自己实现的 TCP 去连接课程提供的真实服务器获取一个网页页面。在浏览器里打开一个网页是别人帮你做完一切但在这里从建立 TCP 连接到发送 HTTP 请求再到解析响应都是你自己的代码完成的。构建与第一次运行拿到模板代码后先按课程说明构建cd repo cmake -S . -B build cmake --build build -j4编译成功后可以先用 Lab 0 阶段要求的webget做一次“连通性测试”。命令格式类似./build/apps/webget cs144.keithw.org /hello如果输出返回了 HTTP 响应正文说明环境、编译器、网络都正常。后面的实验就可以在这个基础上逐步推进。每次完成一个 Lab课程一般会提供对应的测试程序或 checks 目标。运行方式以实验手册写的为准。通用经验是在 build 目录下用ctest或者课程指定的 test target 跑完整测试不要只跑一个用例就提交。5. 功能测试与效果验证CS144 的实验自带测试框架每个 Lab 都是一个“验证关卡”。这里把各阶段测试流程和验证思路拆开讲。5.1 ByteStream 测试做完ByteStream后课程会有一组针对读写顺序、容量限制、EOF 标志的测试用例。运行对应测试程序时看到输出了 PASS 类结果就说明这个基础组件逻辑没问题。测试目的确认内存中的字节流可靠、有序、容量可控。判断标准所有用例通过没有超时和段错误。常见失败容量计算错误、EOF 后还允许写入、读取端读超了。5.2 随机乱序片段重组测试StreamReassembler的测试会随机丢给你各种片段。它可能先给一个索引 5 的片段再给索引 0 的片段还可能给你一段和已有数据重叠的内容。测试目的验证重组器是否能处理乱序、重复、重叠。判断标准最终输出流和原始数据一致。常见失败重叠区间的边界没处理好导致重复字节混进输出流。调试这类问题建议打印每个片段的首尾索引和当前期待的下一字节索引你就知道哪一段是被错误合并的。5.3 TCP Receiver 测试这个测试会验证接收方是否正确解释 TCP segment是否能返回正确的确认号和窗口大小。注意重点考察 SYN/FIN 是否占序号空负载 segment 是否处理正确。测试目的确认接收方能从 TCP 报文还原出字节流并反馈正确的确认信息。判断标准课程测试用例全部通过。常见失败wrap32序号转换写错导致确认号差 1 或差很多。5.4 TCP Sender 测试发送方的测试会模拟各种网络场景丢包、ACK 延迟、接收方窗口为零。你需要确认发送方不会超出窗口发送不会在 1 秒内疯狂重传收到新 ACK 后能及时清理已确认的字节。测试目的验证窗口控制、重传计时器、超时退避。判断标准在丢包模拟测试中最终数据完整送达重传次数合理。常见失败收到 ACK 后没有重置 RTO导致重传风暴。5.5 完整 TCP 连接测试Lab 4 的测试会把你的 TCP 连接放进一个模拟网络环境让它和课程提供的测试端完成多次连接和断开。测试目的验证完整 TCP 状态机包括三次握手、数据传输、四次挥手。判断标准所有测试场景通过没有卡死、没有半开连接。常见失败收到 FIN 后没有“等待对端 ACK”进入错误状态或者连接关闭时状态转换漏了分支。5.6 连真实服务器这是整门课最有仪式感的一步。用你自己写的 TCP 客户端请求http://cs144.keithw.org/类似地址能在终端看到服务器返回的网页内容说明你的协议栈真的能在互联网上通信。这不是模拟是真实链路上的传输。你可以配合抓包工具观察整个过程。5.7 用 Wireshark 亲眼看到数据包课程中很多人会配合 Wireshark 验证自己实现的协议行为。这里给一套通用步骤第一步启动 Wireshark 或 tshark。sudo tshark -i any -f tcp port 80 -w /tmp/class.pcap第二步运行你的 webget 程序或者用任意 HTTP 客户端发起请求。./build/apps/webget cs144.keithw.org /hello第三步打开抓包文件过滤http或tcp.port 80。你会看到一段清晰的交互过程客户端发 SYN。服务器回 SYN-ACK。客户端回 ACK。客户端发 HTTP GET。服务器回 HTTP 200 和正文。双方完成 FIN 挥手。这就是“数据包在网络上完整传输”最直观的验证。用 Wireshark 看自己写的代码产生的流量和直接看操作系统协议栈的流量体感完全不同。以后在网络排障时你会知道一个 TCP 连接从 SYN 到 FIN 每一步都发生了什么。6. 协议接口视角与批量测试机制严格来说CS144 不提供 Web API。但从工程角度看课程实验本身就是一套“接口 批量回归测试”的训练。你把每一层当作一个有明确方法签名的组件上层调用下层测试框架批量跑用例这和你日常做微服务接口联调、跑自动化测试是同一个思路。6.1 组件接口抽象每次实验都会给你一个头文件里面定义了组件接口。比如ByteStream主要方法就是write、read、eof、bytes_written等。你的任务不是改接口而是按接口语义实现内部逻辑。这本身就是最好的工程训练接口已经定死你必须在约束内实现功能同时保证性能。TCP 协议栈也是一样外部只认标准报文格式内部实现你可以自由发挥。6.2 批量跑测试用例课程测试框架支持一次性跑完整组用例。通用做法是进入构建目录后运行cd build ctest --output-on-failure也可以按课程文档说明调用对应 Lab 的检查命令。批量测试的好处是能快速触达边界条件。有些用例会在随机丢包场景下反复测试你的实现如果只在“理想网络”里能跑一进随机测试就会暴露问题。建议把“跑全量测试”做成肌肉记忆cmake --build build -j4 cd build ctest --output-on-failure改动任何代码后先跑当前 Lab 的全量测试确认没有破坏旧功能再开始下一个 Lab。6.3 自己的最小回归脚本除了课程自带测试你还可以写一个简单的 shell 脚本把编译、测试、运行 webget 串起来。这样每次改完代码一键就能得到反馈。#!/bin/bash set -e cmake --build build -j4 cd build ctest --output-on-failure ./apps/webget cs144.keithw.org /hello这个脚本格式是通用模板实际运行时要保证build目录存在且webget参数以课程实验说明为准。把这个脚本加到 git 提交前自测流程里能少交很多次错误版本。7. 资源占用与性能观察CS144 对硬件要求极低因为它的核心不是干重活而是逻辑正确性。编译阶段主要看 CPU 和内存。模板代码规模不大-j4并行编译只需要几百 MB 内存。运行时测试程序也是单机进程不会吃 GPU 显存。你的笔记本在风扇不转的情况下就能完成全部实验。不过性能观察仍然有意义特别是你实现 TCP 时要注意几个容易出性能问题的地方第一个是 ByteStream 的缓冲区实现。如果每次read都移动整个底层 vector 数据数据量一大会产生大量不必要拷贝。合理的做法是用dequechar或环形缓冲把读写操作控制在 O(1) 级别。第二个是 StreamReassembler 的存储结构。如果你用 vector 保存所有未组装片段并且在插入时频繁从头部删除会导致 O(n^2) 复杂度。体感上就是测试数据一大就跑得慢。考虑用有序 map 或队列结构来维护索引区间。第三个是 TCPSender 的重传计时器。实现不好时一个丢包可能引发连锁重传测试运行时间会明显变长。好的实现应该是收到 ACK 时能精确管理已确认字节和仍在空中的字节而不是简单粗暴地整体清空重传计时。调试阶段可以用top或htop观察进程 CPU 占用确认自己没有写出死循环。如果一个测试用例卡住不结束90% 是某个循环边界条件写错少数情况是状态机进了错误分支。这种情况用gdb挂上去打断点比盲目加日志更快。8. 常见问题与排查方法这里整理一下新手最容易踩的坑。问题现象可能原因排查方式解决方案cmake 命令找不到没安装 cmake 或版本过低cmake --versionsudo apt install cmake编译报 C17 特性不支持g 版本太老g --version安装 g-9 以上编译器找不到模板代码仓库地址错误重新核对课程官网地址用课程最新仓库地址重新 clonewebget连不上服务器网络不通或 URL 错误curl -I 目标地址测网络检查网络环境按课程说明使用目标地址测试超时实现里出现死循环或复杂度爆炸用 gdb 打断点查看调用栈检查循环边界、缓存数据结构测试输出错误字节流边界处理有误在关键方法里打印索引和长度重点检查 EOF、重叠片段、读完后的空指针情况Wireshark 看不到回环包普通用户没有抓包权限检查 Wireshark 是否用 root 运行sudo wireshark或授权 dumpcap 抓包跑任务时端口被占上一次服务进程没退出netstat -tlnp查看端口占用杀掉残留进程或换用高位端口TCP 连接卡在 FIN 状态状态机收到 FIN 后动作不对看抓包确认 FIN 后是否回了 ACK对照课程状态图检查实现编译是最常碰到的第一道坎。建议第一步先跑一次最小构建确认工具链没问题再开始写代码。连接真实服务器失败时先用最基础的工具验证网络本身是否通。比如ping -c 3 cs144.keithw.org然后再用系统自带的工具确认 HTTP 服务可用curl -I http://cs144.keithw.org/hello如果系统工具能通而你的 webget 不通问题多半出在 HTTP 请求格式或者 socket 读写逻辑上而不是外部网络。抓包时如果用的是 WSL2需要额外注意 WSL2 的网络栈与真实物理网卡有差异。回环流量和外部流量的表现可能和你预期不太一致。图省心的话抓包阶段最好在原生 Linux 环境下运行。9. 最佳实践与使用建议CS144 适合慢工出细活。说几个能让学习效率翻倍的建议。每次只做一个 Lab不要跳步Lab 0 到 Lab 4 是有依赖关系的。最后一个 TCPConnection 会直接使用前面写的ByteStream、StreamReassembler、TCPReceiver、TCPSender。如果前面某个组件只写了个“能用但边界有问题”的版本后面排查起来会非常痛苦。写代码前先读实验手册不要先搜答案课程实验手册写得非常详细连边界条件都会用文字明示。先读明白手册再写实现。遇到 bug 时也优先回到手册里找边界约束描述而不是直接去抄别人的代码。CS144 的价值就在自己踩坑的过程中复制粘贴会彻底失去意义。用 git 管理每次改动每个 Lab 做完之后打一个 tag 或提交一次。如果后面的实验改崩了可以快速回退。建议提交信息写清楚改动内容例如“finish lab2 tcp receiver”。git add . git commit -m finish lab2 tcp receiver维护一个调试日志开关在 TCP 的发送、接收、重传逻辑里可以预留日志输出开关。正常跑测试时不打印调试时打开。这样你能直接看到每个 segment 的序号、确认号、窗口值定位问题比对着 Wireshark 盲猜更快。把 Wireshark 当作辅助而不是主视角Wireshark 能看到别人的协议栈怎么工作但你要理解的是自己的协议栈为什么不工作。抓包之后要把包里的序号、确认号和自研代码里打印的日志对应起来。收到一个 ACK代码里对应变量应该更新看到超时重传代码里应该有一个计时器到期分支。注意合规与授权边界课程实验涉及真实网络传输和抓包。所有抓包行为都要限定在课程教学服务器、自己本机回环服务或明确授权的测试范围内。不要用 Wireshark 或你写的协议栈去抓取局域网内未经授权的流量更不要用它做端口扫描或数据探测。学网络协议不是学怎么入侵授权边界是最基本的工程素养。10. 总结与下一步CS144 是少有的能把计算机网络从“背诵型知识”变成“工程事实”的课程。你亲手写完ByteStream、StreamReassembler、TCP 接收方、发送方和完整连接后再回头看教材里的三次握手、滑动窗口、超时重传感觉会完全不同。整个过程不依赖显卡普通笔记本就够唯一的门槛是 C 基础和一些耐心。第一个要验证的点很简单先确保 Lab 0 的webget能通过课程服务器拿到 HTTP 响应正文。这一步通了说明你的工具链和网络环境没问题接下来可以安心做协议栈。最容易踩的坑是两个一是 C 编译环境没配好就急着写代码二是跨 Lab 跳步导致底层组件不稳定。只要先跑通最小示例、按顺序完成实验这门课的收益会非常大。做完 Lab 4 之后可以继续往两个方向扩展学习 Lab 5/6 这类进阶实验完成网络接口层和路由器相关实现把视野从传输层扩展到网络层和链路层。用 Wireshark 反复抓自己的 HTTP 请求结合 RFC 793 文档逐字段对照 TCP 报文头。如果你现在正被计网理论折磨不用硬啃。花一个周末把 CS144 的环境搭起来跑通第一个实验你会比看十遍 PPT 更容易理解数据包是怎么在网络中完成传输的。建议先收藏这篇文章等开始做实验时照着操作就行。再补一句学习过程中写一份自己的实验笔记记录每个 Lab 的设计思路、踩过的坑和排查过程。这份笔记在面试时就是最好的项目复盘素材比简历上写“熟悉 TCP/IP 协议”有说服力得多。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻