CS144 2023 Spring课程实验总结
Lab0 ByteStream
配置环境
1 | $ git clone https://github.com/cs144/minnow |
in-memory stream
第一个任务是调用Minnow库用C++封装的sokcet接口写一个应用程序。第二个任务是实现一个内存中的抽象字节流ByteStream,需要知道C++的继承特性、C++11标准库中的string、C++17中的string_view。
cmake
项目使用cmake进行构建,需要了解cmake语法,比如:
cmake_minimum_required(VERSION 3.24.2)
- 指定这个项目所需的最低cmake版本project(minnow CXX)
- 定义了项目名称为 “minnow”,支持的编程语言为C++set(VARIABLE value)
- 定义变量1
2
3
4
5# 定义了一个名为MY_VAR的变量,并将其设置为hello
set(MY_VAR "hello")
# 定义名为MY_LIST的列表变量,并将其设置为包含三个元素的列表
set(MY_LIST "item1" "item2" "item3")${VARIABLE}
- 变量替换语法,将变量的值嵌入到一个字符串中,变量名可以是cmake内置变量、用户定义的变量或其他可用的变量条件执行语句的语法为
1
2
3if(condition expression)
...
endif()include(file)
- include命令用于包含其他cmake脚本文件,将指定的脚本文件中的命令插入到当前的CMakeLists.txt文件中。include
命令可以将大项目分解为多个独立的cmake脚本文件,并在需要的时候包含它们。被包含的脚本文件中定义的变量、函数和宏也可以在包含它的文件中使用。message()
- 输出一条消息到控制台,可以使用四种级别,默认为STATUS级别1
2
3
4
5# STATUS: 输出一般信息
# WARNING: 输出警告信息
# AUTHOR_WARNING: 输出作者级别的警告信息
# FATAL_ERROR: 输出致命错误信息并停止CMake的运行
message([<mode>] "message to display" ...)include_directories()
- 将指定目录添加到包含文件搜索路径中,帮助编译器找到头文件add_subdirectory()
- 添加一个子目录,并在该子目录中执行另一个CMakeLists.txt文件。在顶层CMakeLists.txt文件中使用该命令可以将整个CMake项目拆分为多个子目录和模块,每个子目录和模块都可以独立构建和测试。这种方式可以使CMake项目更加模块化和可维护,使开发人员更容易理解和修改项目的结构和功能。注意:
include
和add_subdirectory
都是CMake中用于包含其他CMake脚本的命令,但它们有一些不同之处。include命令用于在当前CMake脚本中包含另一个CMake脚本,被包含的脚本与当前脚本在同一个CMake运行环境中执行,可以共享变量和命令。add_subdirectory命令用于向CMake项目中添加一个子目录,并在该子目录中执行另一个CMakeLists.txt文件。被添加的子目录具有独立的构建环境,可以定义自己的变量和命令,但与父目录的变量和命令不共享,子目录可以定义自己的库构建规则,并可以使用父目录中定义的变量,但不能修改父目录中的变量。
Lab1~Lab2 TCPReceiver
Lab1 实现Reassembler,Lab2 实现Warp32和TCPReceiver。这个部分用到了一些C++11及以上的特性:
optional
TCPReceiver的send方法的返回值为TCPReceiverMessage结构体,这个结构体中的ackno字段的类型为std::optional<Wrap32>
,std::optional<>
是在C++17标准库中添加的新组件。
在实际编程中,我们通常需要给函数传递参数或在函数体结束前返回值,有时候传递的变量是有明确意义的,但有时又不需要传递有实际意义的值。在过去,这个语义可以由指针来表示,用nullptr
表示无需传递值,但是指针的使用会带来潜在的各种风险。因此,提出了std::optional<>
提供更加安全的表示方式,optional对象内部使用bool
成员变量表示值是否存在,它实际占用的内存最多只比原对象大一个字节。
示例1:使用optional
作为返回值
1 |
|
explicit
Wrap32
的构造函数接收一个uint32_t
类型的参数,因此会作为隐式转换规则,为了阻止不想要的隐式转换,在构造函数前添加explicit
关键词。explicit
只需要加在构造函数的声明前,在.cc
文件中具体实现时不要加。
random ISN
TCP字节流中的每一个字节都有一个序号,初始序列号(ISN)用来表示字节流的起始字节,是一个随机的32位数字,而不是简单的设置为0。因为可能存在这样的情况,上一个采用相同端口号的TCP连接刚刚关闭,它们的字节流可能在网络中传输,如果ISN从0开始,新的TCP连接把上一次连接迟来的数据误认为是自己的数据的可能性很大;此外采用随机的ISN可以增加安全性。
在C++中,<random>
头文件提供了用于生成随机数的类和函数。这个头文件包含了三个主要的类:std::random_device
、std::mt19937
和 std::uniform_int_distribution
。std::random_device
是一个硬件随机数生成器,它提供了一个生成器种子。std::mt19937
类是一个伪随机数生成器,它可以根据给定的种子生成随机数。std::uniform_int_distribution
类是一个分布器,它可以生成均匀分布的整数。
实际上,在C++11标准之前,C和C++程序依赖于C库中的rand
函数产生伪随机数。rand
函数的产生的随机数范围有限(0~32767),且不能产生浮点随机数,不能产生分布不均匀的随机数分布。11标准加入的一些功能解决了这些问题,engine
用于生成一组unsigned
类型的随机数,distributiony
用于指定随机数范围、随机数类型、随机分布类型。
在Minnow中使用的是std::random_device
硬件随机数生成器,在util/random.cc
中定义了如下函数:返回的default_random_engine
对象可以被用于生成随机数,传入Wrap32
的构造函数,作为TCP字节流的ISN。
1 | default_random_engine get_random_engine() |
Lab3 TCPSender
Network Layer
网络层中最重要的概念是**转发(forwarding)和路由()**。转发是指在单个路由器内将数据包从入站链路传输到出站链路。路由涉及网络中所有的路由器,它们通过路由协议的集体交互决定了数据包从源节点到目标节点的路径。
Lab5 down the stack(the network interface)
在这个实验中实现会实现一个NetworkInterface类。
TCP报文有三种传输到远端节点的方式:TCP-in-UDP-in-IP、TCP-in-IP、TCP-in-IP-in-Ethernet,这个实验需要完成最后一种实现方式,将IP地址翻译为Ethernet物理地址,实现eth0、eth1这类接口,主要实现的是ARP地址解析协议。
Internet Protocol
IPv4数据报
- Version number: 指定是IPv4还是IPv6
- Header length: 头部的长度,因为IPv4还有options字段
- Type of Service: 包的类型,可以是实时包、低延时包等类型
- Datagram length: 整个数据报的长度,包括data
- Identifier、flags、fragmentation offset: 用于数据报分片
- Time-to-live: 每经过一跳减一
- Protocol: 传输协议号
- Header checksum: 校验和,仅计算IP头部的校验和
- Source and destination IP addresses: 源IP地址和目标IP地址
- Options: 对于IPv4可选字段,IPv6没有这个字段,如果没有这个字段,IP头就是20个字节
- Data(payload): 传输层头加上应用层数据
IPv6数据报
- Version number: 指定是IPv4还是IPv6
- Traffic class: 类似于TOS
- Flow label: 用于表示一条数据报的流,对一条流中的某些数据报提供更高的优先级,或者对于某些应用提供更高的优先级
- Payload length: 数据长度,IPv6的头部定长40个字节
- Next Header: 类似于IPv4中的Protocol,但是注意IPv6的下一个头部仍然可以是IP头
- Hop limit: 类似于IPv4中的TTL
- Source and destination IP addresses: IPv4是32-bit,IPv6是128-bit
- Data