CS144实验记录

lab0:热身

获取网页

1. 直接访问http://cs144.keithw.org/hello,可以看到返回的是一个HTML页面

2. 现在用telnet命令连接到http://cs144.keithw.org/hello,手动构建一个HTTP请求报文,看是否能得到相同结果,HTTP请求报文的格式如下:

图源中国大学MOOC湖南科技大学计算机网络6.7讲义

可以看到telnet构建http报文发送后成功返回HTML页面的代码

这里官方还留了个小作业,就是试着访问 http://cs144.keithw.org/lab0/sunetid,根据sunetid返回相应的secret code

webget

开始编程部分了,课程要求使用modern C++,给出以下建议:

  1. 使用cppreference.com作为语言参考文档
  2. 不要使用malloc()或者free()
  3. 不要使用new或delete
  4. 杜绝使用裸指针,只在必要时使用智能指针
  5. 避免使用模板、多线程、锁和虚函数
  6. 避免使用C风格字符串和相关的字符串函数,因为很容易出错。可以用std::string代替
  7. 不要使用C风格的强制类型转换,有必要的话可以使用static_cast
  8. 尽量使用const引用传递参数
  9. 使用const声明变量,除非需要更改
  10. 使用const函数,除非需要在函数内修改对象
  11. 避免使用全局变量,并且每个变量应该维持最小的作用域
  12. 交作业前,用cmake --build build --target tidy来检查代码风格,cmake --build build --target format格式化代码

项目里其实已经封装了linux的网络编程接口,先读一下util/socket.hh和util/file descriptor.hh了解以下如何使用(不了解网络编程api可以看下Linux 教程 | 爱编程的大丙 (subingwen.cn)的套接字通信部分)

其实webget就是让我们用封装好的接口模拟上面的telnet,连接到对应网站然后发送一个http请求报文,并且读取对应的响应报文并输出,大概步骤如下:

  1. 创建socket
  2. 绑定地址
  3. 构造HTTP请求报文
  4. 发送报文
  5. 读取相应报文
  6. 输出

测试结果如下

内存中可靠的字节流实现

这个lab要求实现一个能在内存中可靠传输的字节流(不要求线程安全),项目里已经给出了相应的接口设计,我们只需要在src/byte_stream.hh和src/byte_stream.cc里面实现即可

注意,只能在ByteStream类中添加成员变量,在Writer和Reader中实现对应接口。说实话直接看接口描述的话没太看懂怎么用的,结合测试用例才看懂这些接口是什么意思,可以参考byte_stream_***.cc看看是怎么用的

这里简单说下我的理解:

  • Writer
    • push:往缓冲区中写入一段字符串,但是写入字符串长度不能大于可写入的容量,如果大于的话只写入一部分。

    • close:关闭写端,应用层手动设置

    • is_close:是否已关闭,应用层没调用close的话就没关闭

    • available_capacity:剩余可写入的容量

    • bytes_pushed:总共写入了多少字节

  • Reader

    • peek:返回读缓冲的一段字符串,可以是全部,也可以是一部分,看自己实现

    • pop:一般跟peek配合使用,如果peek返回长度为2的字符串,那么一般peek之后要pop(2)

    • is_finished:写端已经关闭并且读缓存为0则返回true

    • bytes_buffered:读缓冲字符串的大小

    • bytes_popped:总共读了多少字节

按照以上思路,我用的是一个环形缓冲区来维护,第一版吞吐量是2.55 Gbit/s

但是看lab文档说实现是有可能大于10Gbit/s(跟电脑配置有关,我配置是i5 9th),于是分析了下实现,发现可能瓶颈在Writer::push的实现,优化了一下重新测试,优化后的吞吐量为12.79Gbit/s

lab1:  将字符串完整拼接存储到字节流中

这次实验是要实现一个重组器,来将网络中发送过来的有可能乱序、重复的包拼接完成后再存入到lab0实现的字节流当中。

课程笔记里给出的Reassembler在协议栈中的作用,负责将包收集后写入到字节流的写端,写完后关闭。

实验文档建议我们最少使用一种数据结构,我的实现逻辑是将传入的字节流用str_cache_来维护,pair里面是字节流的起始和中止索引。

std::map<std::pair<std::uint64_t, std::uint64_t>, std::string> str_cache_

然后再判断ByteStream是否有容量可以写入,有的话就立即写入,如果不能全部写入就把剩下的存回去str_cache_

维护逻辑的实现参考下面这张图,建议也看看实验文档的FAQ。

最后实现是用了将近80行代码,超过了实验要求的上限,没再去优化压行了。

实现的重组器吞吐量在7.75Gbit/s左右

lab2 实现TcpReceiver

本次实验要利用lab0实现的字节流和lab1实现的重组器来实现一个简易的TcpReceiver,ack号意味着TcpReceiver下一个要接收的字节流,流量控制用一个窗口大小来控制,TcpReceiver只接收当前窗口内的字节流。

Tcp中有三种索引,分别是序列号、绝对索引和流索引,它们的关系如下。

绝对索引和流索引之间很容易转换,就是加一和减一的关系。但是序列号和绝对索引之间就没那么容易转换了,序列号是32位的,绝对索引是64位的,起始序列号是随机的,而且序列号在到2^{32}之后又会变为0,所以在传输过程中一个序列号有可能会对应多个绝对索引,这里我们要先实现一个序列号和绝对索引之间的转换器。

这里实现上需要一个checkpoint,来定位到正确绝对索引,绝对索引要取到离checkpoint最近的那一个。

接下来就是TcpReceiver的实现了,这个类已经有一个Reassembler了,按照讲义的流程来讲应该还需要一个Wrap32来存储isn,还有一个uint64的值来存储绝对索引。

实现细节:

  1. 要收到SYN后才开始接收消息,携带SYN的报文也可以携带消息,也可以携带FIN。
  2. 利用上一节实现的Wrap32来实现序列号和绝对索引的转换。
  3. SYN和FIN不是payload的一部分,只是TcpMessage里面的两个flag。
  4. 上一个lab的那张图可以参考

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值