这是整个实验最复杂的部分。
总的流程说实话我并没有完全做出来,抄了大量的作业,主要原因还是 TCP 的状态机确实有点复杂,有很多边缘条件需要考虑,调试起来死活都修不好。
我们需要将 TCPSender 和 TCPReceiver 结合起来变为一个完整的 TCPConnection。尽管在 TCPSender 与 TCPReceiver 等实现时已经完成了实验中所需的所有数据结构与算法部分,但 TCPConnection 最难的其实是它的状态机部分。
如何建立连接?SYN 请求被接收了如何更新 TCPReceiver 和 TCPSender?如何对异常的 segment 处理?如何对 TCPSender 的输出流进行封装、给他加上 ackno 等 header?如何应对 RST 的 header?这些都是必须一一解决的问题。
下图为 TCP 的 FSM 图:

基础工具函数
首先从最简单的部分去实现:
size_t TCPConnection::remaining_outbound_capacity() const {
return _sender.stream_in().remaining_capacity();
}
size_t TCPConnection::bytes_in_flight() const {
return _sender.bytes_in_flight();
}
size_t TCPConnection::unassembled_bytes() const {
return _receiver.unassembled_bytes();
}
size_t TCPConnection::time_since_last_segment_received() const {
return _time_since_last_segment_received;
}
对各个 getter 类函数直接 return 即可。
push_segments_out
由于 receiver 部分涉及到大量状态的转换,所以先去实现 sender 部分。sender 主要要处理:
- 如何写入数据到 stream 内
- 如何给之前 sender 打包好的 seg 加头然后 push 到 segments_out 中
- 如何实现 tick
- 如何结束 sender 流
write 函数
直接将数据压入 stream_in 即可,然后去将 segment 压入 segments_out:
size_t ret = _sender.stream_in().write(data);
push_segments_out();
return ret;
实现 push_segments_out
push_segments_out 需要考虑以下情况:
- 正常数据传输
- SYN 请求与 SYN-ACK 请求
- RST 请求
SYN 请求与 SYN-ACK 请求分别处于两种状态:
- 状态 1:未开始建立连接,主动发送 SYN 请求进行连接建立
- 状态 2:对方请求连接(对方发来 SYN 请求),我方需要发送 SYN-ACK 进行回应
对于状态 1,直接让 sender 执行 fill_window 即可生成一个 SYN 的 seg(因为若要发送 SYN,那么证明当前状态的 sender 的 _syn_flag == false,且 abs_seqno、ackno 等也均开始初始化)。
对于状态 2,由于我们是接收到 SYN 后被动建立连接,此时 TCPReceiver 已经完成了 SYN 的接收,但是 TCPSender 并不知道对方已经发起连接,所以需要通过:
_sender.fill_window(send_syn || in_syn_recv());
这里的逻辑是主动触发 sender 发送 SYN:
- 如果
send_syn = true,代表本端主动建立连接,此时直接调用fill_window(true),允许 sender 发送 SYN。 - 如果
in_syn_recv()为 true,代表当前处于被动连接建立阶段,对端已经发送 SYN,需要回复 SYN-ACK,因此也调用fill_window(true)。
否则正常数据传输阶段:
_sender.fill_window(false);
sender 根据当前发送窗口以及 ByteStream 中的数据生成普通 DATA segment。
封装 TCPSegment
生成 segment 后,需要将 sender 输出的 segment 进行 TCPConnection 层面的封装。
TCPSender 负责的是可靠字节流传输,它只关心:
- seqno
- payload
- SYN
- FIN
- retransmission
但是 TCPConnection 作为 TCP 连接层,需要负责两个方向状态的结合,因此需要给 sender 生成的 segment 添加 receiver 相关信息:
if (_receiver.ackno().has_value()) {
seg.header().ack = true;
seg.header().ackno = _receiver.ackno().value();
seg.header().win = _receiver.window_size();
}
这里实际上完成了 TCP 中最核心的 ACK 复用机制。TCP 是全双工协议,发送方向和接收方向的数据流独立维护。例如:
A ---- data(seq=100) ----> B
A <--- ACK(101) ---------- B
ACK 并不是由 sender 产生,而是由 receiver 根据自己已经成功接收的数据产生。因此 TCPConnection 需要将:
TCPSender 生成: TCPReceiver 生成:
seqno ackno
payload + window size
SYN
FIN
↓
完整 TCP segment
组合起来。
RST 处理
if (_need_send_rst) {
_need_send_rst = false;
seg.header().rst = true;
}
RST 是 TCP 中异常终止连接的机制。例如:
- 收到非法 segment
- 连接不存在
- 对端状态异常
TCPConnection 会设置 _need_send_rst = true,然后在下一次输出 segment 时附加 RST 标志。这里需要注意,RST 通常需要携带 ACK 信息,因此它也应该经过正常的 segment 封装流程。
最终:
_segments_out.push(seg);
将完整 TCP segment 交给外层网络发送。
整体流程如下:
应用层 write()
|
v
ByteStream
|
v
TCPSender.fill_window()
|
v
生成 segment (seq, SYN, FIN, payload)
|
v
TCPConnection.push_segments_out()
|
+------ TCPReceiver 提供 ackno / window
|
+------ 添加 RST
|
v
完整 TCP segment
|
v
_segments_out
bool TCPConnection::in_syn_recv() {
return _receiver.ackno().has_value() && !_receiver.stream_out().input_ended();
}
bool TCPConnection::push_segments_out(bool send_syn) {
_sender.fill_window(send_syn || in_syn_recv());
TCPSegment seg;
while (!_sender.segments_out().empty()) {
seg = _sender.segments_out().front();
_sender.segments_out().pop();
if (_receiver.ackno().has_value()) {
seg.header().ack = true;
seg.header().ackno = _receiver.ackno().value();
seg.header().win = _receiver.window_size();
}
if (_need_send_rst) {
_need_send_rst = false;
seg.header().rst = true;
}
_segments_out.push(seg);
}
clean_shutdown();
return true;
}
segment_received
之后需要实现 segment_received(),这是整个 TCPConnection 最复杂的部分。因为收到一个 segment 后,需要同时影响 sender 和 receiver:
收到 segment
|
+----------------+
| |
v v
TCPReceiver TCPSender
处理数据流 处理 ACK
更新 ackno 滑动发送窗口
产生状态变化 清理 outstanding
|
|
v
TCPConnection 判断状态
|
v
是否回复 ACK / SYN-ACK / RST
其中最大的难点在于 TCP 状态机。例如:
1. 收到 SYN:
LISTEN
|
| SYN
v
SYN_RECEIVED
需要:receiver 接收 SYN、sender 发送 SYN、输出 SYN+ACK。
2. 收到 SYN+ACK:
SYN_SENT
|
| SYN+ACK
v
ESTABLISHED
需要:receiver 接收 SYN、sender 收到 ACK、状态转换。
3. 收到 FIN:
FIN 本质上也是一个占用一个字节序列号的"数据",因此不能简单处理。需要:receiver 判断 FIN 是否连续、ByteStream 输入结束、sender 判断是否发送 FIN。
4. 异常 segment:
例如:未建立连接收到数据、ACK 超过发送范围、RST、非法状态转换——这些都需要严格按照 TCP FSM 处理。
因此 TCPConnection 实际上并没有增加新的可靠传输算法,它更像一个 TCP 状态机协调器。
最终,根据 TCP 状态转移图实现 segment_received:
void TCPConnection::segment_received(const TCPSegment &seg) {
// 检查连接存活状态
if (!_active) {
return;
}
_time_since_last_segment_received = 0;
if (seg.header().ack && seg.payload().size() && in_syn_sent()) {
return;
}
bool send_retry_ack = false;
if (seg.header().ack && _sender.next_seqno_absolute())
send_retry_ack = !_sender.ack_received(seg.header().ackno, seg.header().win);
if (!_receiver.segment_received(seg))
send_retry_ack = true;
if (seg.header().syn && !_sender.next_seqno_absolute()) {
connect();
return;
}
if (seg.header().rst) {
if (in_syn_sent() && !seg.header().ack) {
return;
}
unclean_shutdown(false);
return;
}
if (seg.length_in_sequence_space()) {
send_retry_ack = true;
}
send_retry_ack = send_retry_ack
&& (_receiver.ackno().has_value() && _sender.segments_out().empty());
if (send_retry_ack) {
_sender.send_empty_segment();
}
push_segments_out();
}
clean_shutdown
TCP 关闭相比建立连接更加复杂,因为 TCP 是全双工协议,两个方向的数据流是独立关闭的。也就是说:
- 对方关闭发送方向,不代表连接立即关闭
- 本方关闭发送方向,也需要等待对方确认
- 双方都结束后,TCPConnection 才能进入 inactive 状态
因此需要 clean_shutdown() 来判断当前连接是否可以安全关闭。
半关闭处理
if (_receiver.stream_out().input_ended() && !(_sender.stream_in().eof())) {
_linger_after_streams_finish = false;
}
这里表示对方已经发送 FIN,但是本方还有数据没有发送完成。例如:
A ---------------- data -------------> B
A <--------------- FIN --------------- B
此时 B 的 receiver 已经收到 FIN(_receiver.stream_out().input_ended() == true),但 B 仍然有数据需要发送(_sender.stream_in().eof() == false),说明连接进入半关闭状态。
正常情况下 TCPConnection 会等待一段时间保持连接(_linger_after_streams_finish = true),因为 TCP 关闭后可能还需要接收最后的数据或者 ACK。但这种情况对方已经关闭发送方向,不可能再发送新的数据,因此无需继续 linger:
_linger_after_streams_finish = false;
真正关闭条件
if (_sender.stream_in().eof()
&& _sender.bytes_in_flight() == 0
&& _receiver.stream_out().input_ended())
需要同时满足三个条件:
第一:_sender.stream_in().eof() — 本端应用已经写完所有数据。
第二:_sender.bytes_in_flight() == 0 — 所有已经发送的数据都已经被对方 ACK。因为 TCP 是可靠传输协议,发送完成 ≠ 关闭连接,必须保证所有 segment 都收到 ACK 确认。
第三:_receiver.stream_out().input_ended() — 对方也已经发送 FIN,即双方发送方向均关闭。
三个条件满足后,连接已经逻辑结束。但是 TCP 还需要考虑 linger:
if (!_linger_after_streams_finish
|| time_since_last_segment_received() >= 10 * _cfg.rt_timeout)
如果 _linger_after_streams_finish = false 说明不需要等待直接关闭;否则等待 time_since_last_segment_received() >= 10 * retransmission_timeout 后关闭。原因是 TCP 关闭后仍然可能收到迟到 segment,如果立即释放连接可能无法正确处理这些延迟 segment,因此需要 linger 一段时间。
最后:
_active = false;
表示 TCPConnection 生命周期结束。
所以 clean_shutdown() 本质上是 TCPConnection 的终止状态机。
unclean_shutdown
unclean_shutdown() 负责处理异常关闭,也就是 TCP 连接无法按照正常 FIN 四次挥手流程结束时的情况。其中包括:
- 收到 RST
- 非法状态转换
- 协议错误
- 应用主动终止连接
void TCPConnection::unclean_shutdown(bool send_rst) {
_receiver.stream_out().set_error();
_sender.stream_in().set_error();
_active = false;
首先需要通知上下层连接已经异常终止。
对于接收方向:
_receiver.stream_out().set_error();
表示 TCPReceiver 提供给应用层的输入流发生错误。之后应用读取数据时应该知道不是正常 EOF,而是连接异常。
对于发送方向:
_sender.stream_in().set_error();
表示应用写入发送流时发生错误。此时连接已经不存在,不能继续发送数据。
然后:
_active = false;
直接结束 TCPConnection 生命周期。这和 clean_shutdown() 不同——clean_shutdown() 是正常关闭,需要等待 FIN、ACK 等状态完成;unclean_shutdown() 是立即终止,不需要维护 TCP 状态机。
之后处理是否发送 RST:
if (send_rst)
RST 是 TCP 中用于通知对端「这个连接已经异常终止,不要继续发送数据」。设置 _need_send_rst = true 表示下一次发送 segment 时添加 RST 标志。
但是这里有一个特殊处理:
if (_sender.segments_out().empty()) {
_sender.send_empty_segment();
}
RST 本身需要依附在 TCP segment 上发送。如果 sender 当前没有任何 segment(segments_out 为空),那么没有载体发送 RST。因此生成一个空 segment,然后 push_segments_out() 装载 RST 发送请求。