动手学TCP——CS144实验感想
在Stanford CS144的课程实验Lab0~Lab4中,我们动手实现了一个自己的TCP协议,并且能够真的与互联网通信!此外,感谢Stanford开源本实验并提供了大量的优质测试用例,使得我们仅仅通过互联网就能获取到这么好的学习资源。
本篇博客将从我自己的角度出发简单介绍TCP协议,阐述实现的难点以及在实验过程中的收获。
什么是TCP?
正式的定义以及计网相关的基础知识请读者自行stfw,在此我只简单地从自己角度阐述。
- 从网络协议抽象层来看,TCP是一个传输层协议,用于实现不同主机上进程与进程之间的通信。
- 从TCP提供的服务来看,TCP是可靠数据传输协议,保证将输入的数据保序、保真、不丢失地输出到指定位置。
- 从TCP的核心思想来看,TCP依托于底层不可靠的网络层协议来实现可靠传输,其中的很多设计具有深刻内涵。
TCP的设计有哪些思想?
作为对本课程实验设计者的尊重,本篇博客不会涉及具体的思路,更不会展示代码。我只想聊一聊主要方向。
在深入TCP细节之前,让我们来做一个简单的思维实验:双军问题
在一场攻城战中,己方的两位将军只能派遣信使穿越敌方领土来相互交流。信使可能被逮捕,但他们想要确定一个一起进攻的时间。请问两位将军采取什么策略才能确定一个100%两人都会进攻的时间呢?
将军\(A\)派遣信使传递消息\(M_1\),将军\(B\)收到后传递\(M_2\)回复。为了使得\(B\)确信\(M_2\)被收到,\(A\)又需要发送\(M_3\)....
可以证明,这个问题是无解的。
其实,TCP要解决的就是这样的难题。网络层协议是不可靠的,TCP为了确认数据可靠到达,不得不采取回复机制,即发送方只有正确收到接收方的回复才能确认该数据送达。但回复机制最终还是要面临上述难题————TCP的两端必须确保自己、对方都希望终止连接才能退出。
为了解决这一难题,TCP采用了工程化的办法将误判的概率降到极低。如下图所示,最后一个报文的发送方会等待一段时间(linger time
). 如果这段时间里对方没有发送要求重传的报文则默认对方已收到报文。
思考:如上图所示,先接收到结束信号的主机可以不在最后等一段时间,为什么?
除了结束时的确认机制外,TCP还有很多机制确保数据可靠传输同时保证性能:
- 如上文所说的,为了确认单个报文正确送达引入应答机制
- 为了确保报文之间有序引入计数器机制
- 为了提高效率而不是“一问一答”,引入流水线机制,并通过滑动窗口、回退等技巧保证正确性
- 为了辨别报文丢失和网络拥堵,引入计时机制,同时为了不断逼近当前网络拥堵情况采用指数上升、线性下降法动态调整计时。
......
TCP是一个经典且有效的协议,其设计者早在如今的因特网普及之前就提出了相关思想并因此获得图灵奖。我们在学习网络协议的时候,更重要的是学习这个系统的抽象机制以及各抽象层的设计思想,TCP正是一个巨大的思想宝库。但由于其过于经典,关于它的文章、教材数不胜数,我就不在此一一赘述上述机制的细节了,感兴趣的读者可以自行学习。
CS144如何组织实验?
国内高校的一些课程最不合理的一点就是学习与实践分离,实践与现实疏远。下面我很主观地做一些对比:
课程主要目标:学习C语言程序是怎样最终运行起来的?
- A课程:动手写一个计算机系统模拟器,设计指令集、运行环境、简易操作系统,并最终能运行真实的程序甚至仙剑奇侠传;动手能力强的同学甚至可以用不同方式实现同一抽象层。实践占评分大部分。
- B课程:只有理论课没有实验作业,教材提供的现成模拟器运行的是自创的及其简易的指令集,只能完成一些算术运算操作。期末考背书题占比极高。
课程主要目标:学习计算机硬件组成及设计
- A课程:动手写一个芯片!如果顺利还能成功流片拿到实物。
- B课程:不依托具体指令集讲一些计算机组成中抽象的概念,作业考试大部分都是计算读写速率,平时实验虽然很贴合课程内容,但写成之后唯一的作用就是通过测试用例。
根据我的调查,美国很多大学的课程及其注重实践。比如Stanford CS144学网络,8个Lab就是实现各种网络协议,能够真正和互联网通信;CMU 15-445学数据库设计,Lab就是实现一个完整的的数据库管理系统并管理两百多万条数据......
诚然,这样的实验需要大量的精力去设计,但其对于学生的教育和鼓舞也是极大的。幸运的是国内也有很多优秀的人才做出了这方面的贡献(南大ics、os,一生一芯等)。作为一个资质平平的学生,我可能没有能力为国内计算机教育事业贡献自己,但我希望能尽自己的努力让身边的同学知道有更好的课程,计算机教育不只是自学,不只是背书,不只是调包。
下面回到正题,CS144是怎么把这个实验组织起来使得学生既不需要关注与OS、硬件等交互的细节,又能真正写出一个work的程序的呢?
这张图是他们的实验组成。
在Lab0中,我们调用Linux的TCPSocket实现了自己的wget程序
Lab1~Lab4中,我们实现了自己的TCPConnection并最终替换了上面的Socket使得Lab0中的wget运行在自己的TCP上与互联网连接。
CS144把TCP的实现分为两大部分,Socket和Connection,其中Socket由框架代码直接给出(主要涉及与底层、OS交互,不是TCP的重点),Connection又分为Sender、Receiver两部分让同学分时完成。
这样做一方面降低了实验难度,另一方面也保证了实验成果的有用性。
除此之外,CS144开源了大量的测试用例,其测试脚本编写的也及其易懂、好用,很值得学习。
CS144提供的测试用例覆盖度及其广泛,这在一定程度上确保了自己实现的TCP的鲁棒性。每个实验都会用到之前的代码,很多之前已经通过的代码之后还会找出BUG。在完成Lab4后,大量的测试用例模拟了真实网络环境中的各种情境,如果没有真正理解TCP的每一处细节,很难通过。最后,在自己写的协议上与互联网通信,既是一种收获,也是一次测试(是的,即使通过了所有用例还可能会出现新的bug).
总而言之,CS144通过合理的抽象让学生关注于任务的重心,通过划分降低实验难度,提供大量用例提升实验质量,最终成果也让人颇感欣慰。
总结
- 这几个实验其实代码量并不大(一共不超过500行),但花了我三四天的时间。现在想来,还是没有在一开始理解TCP的很多细节,做的时候存在大量的面向用例编程行为。
- 这次实验再次让我明白了基础设施的重要性(看来PA还是没吃够苦头),gdb、wireshark等设施也是后面printf大法不管用才想起来。
- 通过这次实验,我更坚定了自己学以致用的学习策略。即学到了什么通过实践检验,学得怎么样通过实践效果评判,不读死书。因此之后可能会开数据库的新坑...
- 再次感受到自己能力的不足...还需要再努力寻找更好的学习方法呀
我的水平有限,以上各部分内容难免有疏漏、主观臆断的部分,如有错误、冒犯请指出,感激不尽