CS144-Lab4-TCPConnection

lab 地址 :lab4-doc
代码实现:lab4-code
完整目录:
0. ByteStream
1. StreamReassembler
2. TCPReceiver
3. TCPSender
4. TCPConnection
5. ARP
6. IP-Router

1. 目标

lab4 的目标是结合 TCPSenderTCPReceiver,实现 TCPConnection,完成了 TCPConnection 后 TCP 部分的内容就基本完成了。

image

TCPSenderTCPReceiver 已经完成了基本的收发包操作,支持了定时重传,乱序包重组等特性,接下来需要支持 TCPConnection 的 连接发起/关闭 等功能,根据 TCPConnection 的不同状态,在发包时调整 TCPSegment 的 header flag(ackno,win,rst 等)

这里需要注意的是,lab4 需要在官方提供的 VM 实验环境下运行,否则 ipv4 部分会 timeout ,无法执行通过

2. 实现

这里将 TCPConnection 的几个实现重点划分为如下:

  • 发起连接
  • 写入数据,发送数据包
  • 接收数据包
  • 关闭连接
  • 丰富超时重传机制

2.1 发起连接

发起连接这个功能 TCPSender 基本已经实现了,只要主动调用 TCPSender::fill_window ,就可以填充一个 SYN 包。

void TCPConnection::connect() {
	_sender.fill_window();
	flush_send(); // 将 sender 的 segment push 到 connection 的发送队列中
}

2.2 发包

写入数据,发包的流程也相对比较简单,就是将数据写入 TCPSenderByteStream 中,然后填充窗口发送即可。

size_t TCPConnection::write(const string &data) {
	size_t write_size = _sender.stream_in().write(data);
	_sender.fill_window();
	flush_send(); // 将 sender 的 segment push 到 connection 的发送队列中
	return write_size;
}

2.3 接收数据包

接收数据包的流程主要如下:

  • 调用 TCPReceiver::segment_received 接收数据包
  • 如果是 ack 包,调用 TCPSender::ack_received 更新对端窗口 size 和 对端确认情况。
  • 如果数据包有数据(没数据不需要回复,防止无限 ack),需要回复一个 ack 包
  • 检查连接关闭情况(2.4 展开)
void TCPConnection::segment_received(const TCPSegment &seg) {
	_time = 0;
	// if the rst (reset) flag is set, sets both the inbound and outbound streams to the error state and kills the connection permanently
	if (seg.header().rst) {
		unclean_shutdown();
		return;
	}
	
	_receiver.segment_received(seg);
	if (seg.header().ack) {
		_sender.ack_received(seg.header().ackno, seg.header().win);
	}
	
	// make sure ack
	if (seg.length_in_sequence_space() > 0) {
		_sender.fill_window();
		if (_sender.segments_out().empty()){
			_sender.send_empty_segment();
		}
	}

	flush_send();
	clean_shutdown();
}

2.4 连接关闭

连接关闭的情况比较复杂,主要分为 clean shutdown 和 unclean shutdown。

unclean shutdown

unclean shutdown 的情况比较简单,当收到 TCPSegmentheader.rst 标记为 true 的情况下,此时可以直接关闭连接,同时需要设置 inbound stream 和 outbound stream 为 error 状态。这种方式关闭 TCPConnection 就称为 unclean shutdown

inbound stream 指的是 receiver 的 ByteStream
outbound stream 指的是 sender 的 ByteStream

clean shutdown

clean shutdown 有 3 个前提条件:

  1. 本地 inbound stream 的 unassembled_bytes == 0 (所有字符串都组装完毕)并且已经 end_input ,也就是收到了 Fin 报文。
  2. 本地 outbound stream 已经完全发送完毕(eof == true
  3. 对端成功收到所有 outbound stream 发送的数据包对应的 ack (注意是对端)
    当满足 条件 1 后,根据条件 3 是否百分百成立,会出现两种情况:
  • lingering after both streams end
    条件 2 满足,本地发送的数据已经全部收到 ack,并且已经成功接收到对端的所有数据,此时还需要等待一段时间,因为接受到数据后发送给对端的 ack 可能会丢失,如果直接关闭的话,对方可能会一直尝试重发数据包。
  • passive close
    条件 2 未满足,而条件 1 成立的时候,条件 3 也就确定成立了,这样等到条件 2 满足时,就可以直接关闭连接。
    为什么条件 2 未满足,而条件 1 成立的时候,条件 3 也就确定成立了?这里是结合了 TCPSegment 的可靠性来实现的。由于普通的 ack 包不会重传,那么只要让它带上数据就可以了。当条件 2 未满足的情况下,说明还有数据需要传输(最起码有个 Fin 包),也就是说后续的包都是可靠的,且会带上 ackno,那么对端在收到包的时候,就能确定自己发送的包都被成功接收了。
    那么此时条件 3 也就满足了,当条件 2 也满足的时候,就能直接关闭了。

3. 测试

测试过程中犯的几个错误:

  • 没有按照对端窗口大小发包,发了额外的数据包,对端丢弃了,导致需要重传,进而导致 test case 的执行时间超时了。
  • 超时时间等配置需要读取 TCPConnection::cfg 的配置变量,而不是直接读取 TCPConfig 中的静态常量,test case 中会动态调整 cfg 的配置,不使用 cfg 在某些情况下会导致超时(配置的重传时间比 TCPConfig 中的短)

image

posted @ 2023-01-25 10:27  lawliet9  阅读(279)  评论(0编辑  收藏  举报