CS144实验注意点
CS144 实验注意点
TCP implement
ByteStream
- 看测试,通过所有测试即可
- 重要的是 input_ended() 、end_input() 和 eof() 的 区别
- input_ended 是 查看 byte_stream 是否 已经被 写入 eof
- end_input 是 提供给 writer 的,用来 表示 要输入的数据 已经输入完了
- eof 是 用来查看 byte_stream 中的数据是否已经被 reader 读完
Reassembler
- 看测试,通过所有测试即可
- 最吃性能的地方,如果这里复制了大量的字符串,就会导致 throughout 较低
- 最重要的是
1. 需要维护 unreassemble bytes,并 进行重新排序,选择合适的数据结构
2. 在 组合完成后,需要将 有序的数据 写入 Bytestream
3. 需要 维护 下一个期待的 absolute_seqno_idx,这就是 下一个包的 确认号 的 索引
4. 携带 fin 的包可能提前到达,需要 在 内部维护好 它,并在所有数据都到达后,对 bytestream 进行 end_input
5. 要尽可能地减少 数据的拷贝,所以 最好将 数据的校验 放在 这里
6. 可以选择使用 index 进行标记,存储 shared_ptr 而不是 数据,仅仅释放 无效的数据
或者根据 要 复制的数据的大小,选择 copy or 使用索引
7. 整个 window_size (capacity) = unread bytes + win (待发送的可接受的窗口大小)
包括 unreassemble bytes
WrapInt32
- tcp header's ackno and seqno is uint32_t
- absolute_seqno/absolute_ackno = actual seqno/ackno + _isn
- absolute_seqno_index, actually is data_index without SYN and ACK
- absolute_seqno = seqno + _isn
- absolute_seqno_index = absolute_seqno - 1 (SYN)
- unwrap 是将 WrapInt32 使用 _isn 和 checkpoint
- 在 多个 2^32 的 区域内选择一个 离 checkpoint最近的
- 使用 checkpoint 可以 查看在 哪几个 2 ^32 的区域
- 进行比较后,得到距离 checkpoint 最近的 uint64_t 的 值,确保 该值 > _isn
- 得到 uint64_t 后,减去 _isn 即可得到 有效的 absolute_seqno/ackno
Receiver
- receiver concerns SYN FIN Payload Seqno
- 还是要看测试
- 有限状态机可以帮助处理各种棘手的条件判断
- 这里数据的有效性判定,既可以在这里判定,也可以交给 Reassembler 来 判断
- 还是交给 Reassembler 来判断比较好,这样不需要复制字符串
- Payload 是 Buffer 类型,其中的 数据实际上是一个 智能指针,所以可以随便拷贝
- 最好将 Payload 的 数据 直接 拷贝到 字符串 中
- 这里的 window_size,指的是 Byte_stream 中 的 capacity - unread bytes (to send by next tcpsegment)
- 每个 sender 和 receiver 都有一个 Byte_stream
- sender's Byte_stream => write data from user, read data for sender to send
- receiver's Byte_stream => write data from receiver, read data for user
- 每个 sender 和 receiver 都有 一个 window_size
- receiver's window_size = capacity - unread bytes (will be sent by next tcp segment's win field)
- sender's _window_size, in fact is the number of bytes that can send
- receive window_size from tcp_segment's win field
- waiting for retransmission the number of byte that can send
- bytes_in_flight _window_size
- window_size (win field of tcpsegment)
- sender can receive data size from user, is the capacity of byte_stream
Sender
- sender concerns ack and win in tcpsegment
- tick() => 用于重传 _wait 队列中的 超时的 segment
- fill_window() => 填充所有可以发送的 _window_size
- firstly invoke fill_window, will send SYN tcp segment
- segment_receive(ackno, win) => 将 确认的 ackno 全都 从 _wait 中删除
=> 更新 _window_size 通过 传入 的 win
=> 传来的 window_size = _window_size + bytes_in_flight()
- 每次虽然可以发送多个 tcpsegment,但是 只有 第一个 tcp segment 可以 进行超时重传
- 剩下的 tcpsegment 仅仅 发送一次,不进行重传
- 当 第一个 tcp_segment 被确认后,将 下一个 未确认的 tcp_segment 的 超时重传开启
- 重传的标志
- _ddl_tick (截止时间) = _tick (活跃的 ticks) + rt_time (超时时间)
- 下一次超时,用当时的 _tick + power(2, 第n次重传) * rt_time
- 不能使用 上一次的 ddl_tick
Connection
- 只需要 把 receiver 和 sender 关心的东西 传给 他们即可
- ack 不会 被 重传,所以 connection 每次需要传输
- as server
- LISTEN 状态,实际上就是 什么都不做的状态,只分配一个 receiver 和 sender (connection)
这样,它就有了收发数据包的能力
- LISTEN 状态收到 ack 和 rst,直接忽略;接收 SYN 就相当于 服务器收到 连接请求
- 需要传递给 receiver
- RST 必须 使用 有效的 seqno,才能被接收,并断开连接并设置 流为 error 状态
- 每个 有长度 的 tcp_segment 必须被回应一个 tcpsegment,也就是必须被确认
- 单独的 ack 不需要被重传,所以 通过 sender 获取有效的 seqno 后,由 connection 发送即可
- 不需要有数据
- 每个 从 sender 中 读出的 segment,都使用 receiver's ackno 和 win_size
- 从 _sender.segment_out 获得 tcp segment 后,得到 seqno、payload、SYN、FIN
- 查看 _receiver 中 得到 ackno 和 win
- 如果 ackno 是空,说明 尚未收到 SYN,适用于 client 调用 connect 时 发送 SYN
- as client
- use connect, will invoke _sender.fill_window() 发送 SYN
- server 收到 SYN,_receiver 初始化了 ackno,_sender 调用 fill_window 发送 SYN
- 从 connection 的 segment_out 中拿到 tcp_segment
- 从 receiver 得到的 ack 和 win
- 放到 connection 的 segments_out
- cleanly finished
- before receive fin, send fin will linger. means that firstly send fin => linger
- after receive fin, send fin will not linger, means that send fin secondly => no linger
- judge after _sender.segment_receive(ack, win)
- the sign that send fin
- _sender.next_seqno_absolute == _sender.stream.bytes_written() + 2;
- SYN_SENT
- 1 < 2
- ESTABLISHED
- 1 + sender's bytes < all written bytes + 2
- FIN_SENT(FIN_WAIT1)
- 1 + all data + 1 == all data + 2
- the sign that fin is acked
- _sender.next_seqno_absolute == _sender.stream.bytes_written() + 2 => fin sent
- bytes_in_flight() == 0 => fin is acked
- judge after _receive.segment_receive()
- the sign that receive fin
- _receiver's stream_out.input_ended == true
- tick()
- 用于 最后的 TIME_WAIT 状态,到达时间后 设置 _active 为 false
- 用于 调用 sender 的 tick,重传 超时的 tcp_segment
测试
- revisit webget
- tun144 and tun145 network interface must be open
- can not have any output to pass the test, in other word, must clean the debug info before test
- must make check_lab4, then tun144 tun145 is open, then use webget/tcp_ipv4 to send requests
bugs
- tcp_receive 中,增加 _ackno 时,需要使用 实际写入数据的 absolute_seqno
LISTEN 状态
-
等待 SYN,其他的没有什么特别的地方
-
TCP 重点就是 connection,而 listen 仅仅是 创建一个 tcp connection,等待 SYN
-
client 调用 connect 是 发送 一个 SYN,通过调用 _sender.fill_window(),_reveiver 没有 初始化 _ackno,所以 不带 ACK
-
server 收到 后,_receiver 初始化了 _ackno, _sender 调用 fill_window(),所以 SYN 会带 ACK
重点
-
查看 tcp_state.hh/.cc 查看 tcp 的 状态转换
-
查看 如何使用 tun 和 tap
-
查看如何 写测试
-
查看工程经验,eg: list 和 map 删除当前节点
-
container.erase(iter++); => iter 已经 指向下一个,而且还可以使用当前的值
-
不需要使用中间变量
-
最后画图,看 sponge 网络库的实现
-
tcp 状态转换图,查看 RFC 的文档
下一步计划
- 首先修改 receiver 和 Reassembler,由 Reassembler 全权处理数据
- 理清每一个组件的关系 和 功能,重新设计类的实现
- 使用真正面向对象的方法
- 理清各个组件的功能之后,将其总结在 一个 博客文章中,待下次实现时 参考,尤其是 其中的一些坑点
- 查看别人的实现,慢慢修改 自己的实现,画图理清逻辑
- eg: unwrap 的 实现
-
receive 查看数据有效性的 实现 -
如何更好地组织代码
cs144 框架
-
webget 应用层 => lab0
-
tcp 传输层 => lab0 (bytestream), lab1 (reassembler), lab2 (sender), lab3 (receiver), lab4 (connection)
-
ip 网络层 => lab 自带
-
2.5 layer
-
network interface arp 协议 => lab5 (ens33, lo, tun144, tap10)
- 重点是 接收 和 解封 EthernetFrame,可能时 ARP 包,也可能是 IPV4 包
- 可能是 ARP Request 或 Response
- 需要维护一个 cache for ip to mac -
link 以太网 frame,lab 自带
-
router
-
进行匹配,最长前缀匹配,然后进行转发,转发到 对应 的 network interface 即可
-
如果有 next_hop,说明目的地址在下一个路由器内部
-
如果没有 next_hop,说明目的地址 在 路由器
-
有多个 network interface
-
router 只需要改变 目的 IP 地址即可,即 只查看 IP 数据包
-
不 查看 EthernetHeader 和 TCPHeader -
不需要管 端口号 和 MAC 地址,ARP 由 network interface 自己去做 -
保持最大程度的简单 -
route table entry
-
一个路由器有多个 interface num -
route_prefix prefix_length(mask) next_hop interface num -
子网前缀 子网掩码 下一跳 网络接口号 -
route_prefix 可能是 0.0.0.0,即 默认路由
-
tcp in udp in ip
- 使用 udp 传输 tcp 的 数据,可以 使用 kernel 提供的 API
tcp in ip
- tun network interface 以上的包头 application 添加
tcp in ip in Ethernet
- tap network interface 也自己添加

浙公网安备 33010602011771号