CS144 Lab 特别篇:单元测试


2.3.1 Test 语句

send_extra.cc 中,助教给了很多用例。这些用例我认为如果可以在编码之前就看的比较明白,就可以帮助我们节省很多时间,也可以帮我们理解 sender 发挥的作用~

首先我们需要理解这几个 test 语句:

test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));

TCPSender 发送 SYN 段(序列号为 isn),无数据负载。

ExpectSegment{} 表示的是当前发送了一个 segment,这个 segment 在这个时候已经被 push 到了 this->_segments_out 中。

它表示当前 sender 发送了一个段。发送了一个 segment。

后面一系列的 with 方法实际上就是验证当前 segment 的属性是否正确,如果不符合预期则会抛出异常。

test.execute(ExpectNoSegment{});

同理可得,当前没有 segment 被发送。也就是说,test 无法在 this->_segments_out 中取到新的 segment。

这里要额外说明一种情况就是:

test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectNoSegment{});

实际上它表示的就是 segment 从 this->_segments_out 中被取出来之后,this->_segments_out 里面就再也没有了其他数据。这种情况下适合检查的是,this->_segments_out 里面有好几个 segment 要连着发送的情况。

test.execute(WriteBytes("abc"));

TCPSender 将 "abc" 写入发送缓冲区。它实际上会执行一次 sender.fill_window() 这意味着通过写入缓冲区内的数据可以在这个过程中被立马放入 this->_segments_out

sender_harness.cc

struct WriteBytes : public SenderAction {
    void execute(TCPSender &sender, std::queue<TCPSegment> &) const {
        sender.stream_in().write(std::move(_bytes));
        if (_end_input) {
            sender.stream_in().end_input();
        }
        sender.fill_window();
    }
}
test.execute(Tick{rto - 5});

时间流逝 RTO-5 个时间单位,未触发超时。

Tick 都表示当前时间是逐步在流逝的。测试框架会主动触发时间的流逝这一「行为」。和现实世界中的流逝是毫无关系的。

一般来说,Tick 常常用于测试重传机制相关的内容。如果有如下过程:

test.execute(Tick{rto - 5});
test.execute(6);

这表示过了 RTO-5 个时间单位后,再过 6 个时间单位。这其中过了一个 RTO 单位,一般情况下会触发重传一次,也会触发一次回退。

test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(1000));

接收方确认 SYN,ACK 号为 isn+1(表示期望下一个字节是 isn+1)。

这表示 sender 接受到了一个 Ack,实际上会执行 this->ack_received() 方法。z之后也会直接开始执行 sender.fill_window(); 方法。

struct AckReceived : public SenderAction {
    void execute(TCPSender &sender, std::queue<TCPSegment> &) const {
        sender.ack_received(_ackno, _window_advertisement.value_or(DEFAULT_TEST_WINDOW));
        sender.fill_window();
    }
};
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});

这个就真的是顾名思义了,它需要检查你当前的 sender 处于哪个状态。

你可以通过 tcp_helpers/tcp_state.cc 来得知 test 框架判断 sender 处于哪个状态的依据。

2.3.2 初始情况

一般来说,测试环境都会有一个 SYN —> ACK 的过程,其实就是我们熟悉的三次握手。它大概可以用以下过程来描述:

  1. 发送 SYN 段(建立连接)
    TCPSender 发送 SYN 段(序列号为 isn),无数据负载。

  2. 接收 SYN-ACK 确认
    接收方确认 SYN,ACK 号为 isn+1(表示期望下一个字节是 isn+1)。窗口大小设为 1000,允许发送方传输数据。

对应代码为:

test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(1000));
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});

2.3.3 情况 1

过程描述:
验证当 TCPSender 已启动且定时器在运行时,发送新数据段是否会干扰现有定时器。
根据测试名称,正确行为是:定时器应继续运行,不会被新发送的段重置,从而保证超时重传的可靠性。

详细过程:
(从初始情况开始)

  1. 写入数据 "abc" 并发送

    • "abc" 写入发送缓冲区。
    • 发送数据段,序列号为 isn + 1,负载为 3 字节的 "abc"。此时启动重传定时器(RTO 初始值)。
  2. 时间流逝

    • 时间流逝 RTO - 5 个时间单位,未触发超时。
    • 确认没有重传发生,即没有新的数据段被发送。
  3. 写入新数据 "def" 并发送

    • "def" 写入发送缓冲区。
    • 发送新数据段 "def",序列号从 isn + 1 + 3 开始(这里代码未显式体现序列号,默认按照顺序)。新数据发送后,原有定时器仍在运行,未被重置。
  4. 时间流逝

    • 时间流逝 200 个时间单位,假设 RTO - 5 + 200 > RTO,超过 RTO 触发超时。
    • 重传最早的未确认段 "abc",序列号为 isn + 1,证明定时器未被新数据段重置。
    • 确认没有其他段被发送(如 "def" 尚未超时)。

关键结论:

  • 定时器独立性:TCPSender 在发送新数据时,不会重置已有的重传定时器。定时器始终跟踪最早未确认的段(此处为 “abc”)。
  • 重传优先级:超时后优先重传最早的未确认数据,确保可靠性,避免旧数据丢失导致传输中断。
  • 窗口管理与多段处理:新数据 “def” 在窗口允许下被发送,但其确认与否不影响 “abc” 的定时器逻辑。

2.3.2 情况 2

过程描述:验证 fill_window() 方法是否能正确填充一个较大的窗口。根据测试名称,正确行为是:fill_window() 方法应根据窗口大小和数据量,合理地将数据分段发送,确保每个数据段的大小符合配置要求,且序列号连续,以充分利用窗口空间。

这个过程比较清晰的描述了一次 fill_window() 最大可发送量为:

const size_t expected_size = min(TCPConfig::MAX_PAYLOAD_SIZE, min(bigstring.size(), window_size) - i);

假设此时发送端缓冲区很长,超出 expected_size 的部分仍然不能发送。

详细过程
(从初始情况开始)

  1. 循环发送数据段
    • 开始一个循环,循环条件是 i + TCPConfig::MAX_PAYLOAD_SIZE < min(bigstring.size(), window_size),其中 i 是当前已经处理的数据偏移量。
    • 在每次循环中,计算期望的数据段大小 expected_size,取 TCPConfig::MAX_PAYLOAD_SIZE 和剩余可发送数据量(min(bigstring.size(), window_size) - i)中的较小值。
    • 发送一个数据段,该数据段没有额外标志,负载大小为 expected_size,数据为 bigstring 从偏移量 i 开始长度为 expected_size 的子串,序列号为 isn + 1 + i

关键结论

  • 窗口填充正确性fill_window() 方法能够根据窗口大小和数据量,正确地将数据分段发送,确保每个数据段的大小不超过配置的最大负载大小,充分利用了窗口空间。
  • 序列号连续性:每个数据段的序列号是连续的,保证了接收方能够正确地按顺序接收和重组数据,维护了数据传输的有序性。
  • 数据完整性:通过循环发送数据段,将整个 bigstring 数据按要求分段发送,确保了数据的完整性,避免了数据丢失或错误传输。

2.3.3 情况 3

一句话:FIN 和 SYN 标志位都同样需要重传机制。含有 FIN 标志位的 segment 中,无论是否携带了其它数据,也都需要有重传机制。

过程描述:验证包含 FIN 标志的数据段是否能像其他数据段一样进行重传。根据测试名称,正确行为是:当包含 FIN 标志的数据段未被确认且超时发生时,应像普通数据段一样进行重传,以确保连接的正常关闭。

详细过程
(从初始情况开始)

  1. 写入数据并发送含 FIN 的数据段
    • "abc" 写入发送缓冲区,并标记输入结束(with_end_input(true))。
    • 发送一个数据段,负载为 3 字节的 "abc",序列号为 isn + 1,同时带有 FIN 标志,表示连接关闭请求。
  2. 时间流逝(未触发超时)
    • 时间流逝 RTO - 1 个时间单位,未触发超时。
    • 确认没有重传发生,即没有新的数据段被发送。
  3. 时间继续流逝(触发超时)
    • 时间再流逝 2 个时间单位,此时总时间超过了 RTO,触发超时。
    • 重传之前带有 FIN 标志的数据段,负载为 3 字节的 "abc",序列号为 isn + 1,FIN 标志保持为 true

关键结论

  • FIN 段重传机制:包含 FIN 标志的数据段在未被确认且超时发生时,会像普通数据段一样进行重传。这确保了即使在连接关闭阶段,数据的可靠传输也能得到保障。
  • 可靠性保障:通过重传 FIN 段,保证了连接关闭请求能够被接收方正确收到,避免因丢包导致连接无法正常关闭,维护了 TCP 连接的可靠性。
  • 定时器一致性:对包含 FIN 标志的数据段使用与普通数据段相同的定时器和重传逻辑,体现了 TCPSender 重传机制的一致性和稳定性。

2.3.4 情况 4

什么情况下需要添加 FIN 标志位?

过程描述
验证在发送数据段时,如果添加 FIN 标志会使数据段超出接收方窗口大小,是否不会添加 FIN 标志。根据测试名称,正确行为是:当添加 FIN 标志会导致数据段大小超过接收方窗口时,不添加 FIN 标志,直到窗口大小足够容纳含 FIN 标志的数据段。
验证当接收方窗口已满时,是否不会单独发送仅含 FIN 标志的数据段。根据测试名称,正确行为是:在窗口空间不足的情况下,不会单独发送 FIN 段,直到窗口有足够空间,以保证数据传输的合理性和可靠性。

详细过程 1
(从初始情况开始)

  1. 写入数据并准备关闭连接
    • "abc" 写入发送缓冲区,并标记输入结束(with_end_input(true)),表示有关闭连接的需求(即要添加 FIN 标志)。
  2. 接收 ACK 并发送普通数据段
    • 模拟接收到对 SYN 段的 ACK,确认号为 isn + 1,窗口大小为 3。
    • 发送一个数据段,负载为 3 字节的 "abc",序列号为 isn + 1,无 FIN 标志,因为此时添加 FIN 可能会使数据段超出窗口大小。
  3. 多次接收 ACK 但不发送新段
    • 模拟接收到确认号为 isn + 2,窗口大小为 2 的 ACK,此时窗口大小不足以添加 FIN 标志,确认没有新的数据段被发送。
    • 模拟接收到确认号为 isn + 3,窗口大小为 1 的 ACK,同样窗口大小不够,确认没有新的数据段被发送。
  4. 接收合适 ACK 并发送含 FIN 段
    • 模拟接收到确认号为 isn + 4,窗口大小为 1 的 ACK,此时窗口大小足够容纳一个仅含 FIN 标志(无数据负载)的数据段。
    • 发送一个仅含 FIN 标志、无数据负载的数据段,序列号为 isn + 4

详细过程 2
(从初始情况开始)

  1. 写入数据但不发送
    • "abc" 写入发送缓冲区,但由于此时还未收到合适的 ACK 来确定窗口大小,所以没有数据段被发送。
  2. 接收 ACK 并发送普通数据段
    • 模拟接收到对 SYN 段的 ACK,确认号为 isn + 1,窗口大小为 3。
    • 检查 TCPSender 状态,期望状态为 SYN_ACKED
    • 发送一个数据段,负载为 3 字节的 "abc",序列号为 isn + 1,无特殊标志。
  3. 请求关闭连接但不发送 FIN 段
    • 调用 Close 操作,表示要关闭连接,即需要发送含 FIN 标志的数据段。
    • 但由于此时窗口空间可能不足以单独发送 FIN 段,确认没有新的数据段被发送。
  4. 多次接收 ACK 仍不发送 FIN 段
    • 模拟接收到确认号为 isn + 2,窗口大小为 2 的 ACK,窗口空间仍不足,确认没有新的数据段被发送。
    • 模拟接收到确认号为 isn + 3,窗口大小为 1 的 ACK,窗口空间还是不足,确认没有新的数据段被发送。
  5. 接收合适 ACK 并发送含 FIN 段
    • 模拟接收到确认号为 isn + 4,窗口大小为 1 的 ACK,此时窗口有足够空间容纳一个仅含 FIN 标志(无数据负载)的数据段。
    • 发送一个仅含 FIN 标志、无数据负载的数据段,序列号为 isn + 4

关键结论

  • 窗口限制机制:TCPSender 会根据接收方的窗口大小来决定是否添加 FIN 标志。当添加 FIN 会使数据段超出窗口大小时,会等待窗口大小足够,体现了窗口管理对数据段发送的限制作用。
  • 窗口限制下的 FIN 发送策略:TCPSender 会根据接收方的窗口大小来决定是否单独发送 FIN 段。当窗口已满或空间不足时,不会单独发送 FIN 段,避免不必要的数据段发送,体现了窗口管理对数据传输的控制作用。
  • 可靠性与效率平衡:通过不添加 FIN 避免数据段溢出窗口,保证了数据传输的可靠性,同时在窗口允许时及时发送含 FIN 段,实现了连接关闭的高效性。
  • ACK 与窗口管理协同:ACK 的接收不仅更新了确认号,还影响了窗口大小,从而决定了数据段的发送策略,体现了 ACK 处理与窗口管理的协同工作。

2.3.5 情况 5

TCPConfig::MAX_PAYLOAD_SIZE 仅对数据段的负载部分起限制作用,SYN 和 FIN 标志不受此限制。以确保数据段的正常发送和连接的正确关闭。

过程描述:验证 MAX_PAYLOAD_SIZE 仅对数据段的负载部分起限制作用。根据测试名称,正确行为是:在发送数据段时,数据段的负载大小应受 MAX_PAYLOAD_SIZE 限制,但其他标志(如 FIN 标志)不受此限制,以确保数据段的正常发送和连接的正确关闭。

详细过程
(从初始情况开始)

  1. 写入大量数据并准备关闭连接
    • 将一个较大的字符串 bigstring 写入发送缓冲区,并标记输入结束(with_end_input(true)),表示要关闭连接(添加 FIN 标志)。
  2. 接收 ACK 并发送数据段
    • 模拟接收到对 SYN 段的 ACK,确认号为 isn + 1,窗口大小为 40000。
    • 发送一个数据段,负载大小为 TCPConfig::MAX_PAYLOAD_SIZE,数据为 bigstring,序列号为 isn + 1,同时带有 FIN 标志。这表明即使数据量较大,但负载大小被 MAX_PAYLOAD_SIZE 限制,而 FIN 标志正常添加。
  3. 检查状态
    • 检查 TCPSender 状态,期望状态为 FIN_SENT,确认包含 FIN 标志的数据段已成功发送。
  4. 接收 ACK 并再次检查状态
    • 模拟接收到确认号为 isn + 2 + TCPConfig::MAX_PAYLOAD_SIZE 的 ACK。
    • 检查 TCPSender 状态,期望状态为 FIN_ACKED,确认包含 FIN 标志的数据段已被正确确认,连接关闭流程正常完成。

关键结论

  • MAX_PAYLOAD_SIZE 限制作用MAX_PAYLOAD_SIZE 仅对数据段的负载部分起限制作用,确保负载大小不会超过设定值,维护了数据段的合理大小。
  • 标志独立性:FIN 标志等控制标志不受 MAX_PAYLOAD_SIZE 的限制,可以正常添加到数据段中,保证了连接关闭等控制功能的正常实现。
  • 状态转换正确性:通过正确的 ACK 接收和状态检查,验证了 TCPSender 在发送和确认含 FIN 标志数据段过程中的状态转换是正确的,确保了连接管理的可靠性。

2.3.6 情况 6

验证在填充窗口时,将窗口大小为 0 的情况视为窗口大小为 1 进行处理。

过程描述:验证在填充窗口时,将窗口大小为 0 的情况视为窗口大小为 1 进行处理,同时不进行重传超时时间(RTO)的回退操作。根据测试名称,正确行为是:当窗口大小为 0 时,仍能按顺序发送数据段,且每次超时后重传未确认的数据段,RTO 保持不变。

但是,非 0 窗口则需要严格限制,一旦 flight 中的 data 容量大于窗口容量,这个时候必须处于停止发送的状态中。

详细过程
(从初始情况开始)

  1. 接收窗口大小为 0 的 ACK 并发送数据段
    • 模拟接收到对 SYN 段的 ACK,确认号为 isn + 1,窗口大小为 0
    • 检查 TCPSender 状态,期望状态为 SYN_ACKED
    • 发送一个负载大小为 1 字节、数据为 "a"、序列号为 isn + 1 的数据段,无其他特殊标志,体现了将窗口大小 0 视为 1 来发送数据。
  2. 请求关闭连接但不发送新段
    • 调用 Close 操作,表示要关闭连接,但由于窗口大小限制,此时没有新的数据段被发送。
  3. "a" 数据段进行多次超时重传
    • 进行 5 次循环,每次先让时间流逝 RTO - 1 个时间单位,期间没有数据段发送。
    • 再让时间流逝 1 个时间单位,触发超时,重传负载为 "a"、序列号为 isn + 1 的数据段,且每次重传的 RTO 没有回退。
  4. 接收新 ACK 并发送 "b" 数据段及重传
    • 模拟接收到确认号为 isn + 2、窗口大小为 0 的 ACK。
    • 发送一个负载大小为 1 字节、数据为 "b"、序列号为 isn + 2 的数据段。
    • 同样进行 5 次循环,每次先让时间流逝 RTO - 1 个时间单位,无数据段发送,再流逝 1 个时间单位触发超时,重传 "b" 数据段,RTO 保持不变。
  5. 接收新 ACK 并发送 "c" 数据段及重传
    • 模拟接收到确认号为 isn + 3、窗口大小为 0 的 ACK。
    • 发送一个负载大小为 1 字节、数据为 "c"、序列号为 isn + 3 的数据段。
    • 进行 5 次循环,每次先让时间流逝 RTO - 1 个时间单位,无数据段发送,再流逝 1 个时间单位触发超时,重传 "c" 数据段,RTO 不回退。
  6. 接收新 ACK 并发送含 FIN 段及重传
    • 模拟接收到确认号为 isn + 4、窗口大小为 0 的 ACK。
    • 发送一个负载大小为 0 字节、序列号为 isn + 4、带有 FIN 标志的数据段。
    • 进行 5 次循环,每次先让时间流逝 RTO - 1 个时间单位,无数据段发送,再流逝 1 个时间单位触发超时,重传含 FIN 标志的数据段,RTO 保持不变。

关键结论

  • 窗口大小处理策略:当接收方窗口大小为 0 时,TCPSender 将其视为窗口大小为 1 来处理,能够按顺序发送数据段,保证了数据的持续传输。
  • RTO 稳定性:在多次超时重传过程中,RTO 没有进行回退操作,保持了定时器的稳定性,确保了重传机制的可靠性。
  • 数据和控制信息传输:无论是普通数据段(如 "a""b""c")还是含 FIN 标志的控制数据段,都能在窗口大小为 0 的情况下正常发送和重传,保证了数据和连接管理的正常进行。

2.3.7 情况 7

过程描述:验证重复的 ACK 和过时的 ACK 对 TCPSender 的正常工作没有影响。根据测试名称,正确行为是:发送方在接收到重复或过时的 ACK 时,不会产生异常行为,仍能按照正常逻辑处理数据发送和连接关闭操作。

详细过程
(从初始情况开始)

  1. 写入数据并发送数据段
    • "abcdefg" 写入发送缓冲区。
    • 发送一个负载大小为 7 字节、数据为 "abcdefg"、序列号为 isn + 1 的数据段。
  2. 接收正确 ACK 及重复 ACK
    • 模拟接收到确认号为 isn + 8 的 ACK,窗口大小为 1000,表明 "abcdefg" 已被正确接收。
    • 多次重复接收确认号为 isn + 8 的 ACK,期间没有新的数据段被发送,确认重复 ACK 不影响发送方。
  3. 接收过时 ACK
    • 多次接收确认号为 isn + 1 的过时 ACK,没有新的数据段被发送,说明过时 ACK 不会干扰发送方的正常状态。
  4. 写入新数据并发送含 FIN 段
    • "ijkl" 写入发送缓冲区,并标记输入结束(with_end_input(true))。
    • 发送一个负载大小为 4 字节、数据为 "ijkl"、序列号为 isn + 8 且带有 FIN 标志的数据段。
  5. 多次接收各种 ACK
    • 多次接收确认号为 isn + 1isn + 8isn + 12 的 ACK,包括重复和过时的 ACK,发送方未产生异常。
  6. 超时重传
    • 时间流逝 5 * RTO,触发超时,重传负载为 "ijkl"、序列号为 isn + 8 且带有 FIN 标志的数据段。
    • 之后确认没有新的数据段被发送。
  7. 接收最终 ACK 并检查状态
    • 模拟接收到确认号为 isn + 13 的 ACK,以及再次接收确认号为 isn + 1 的过时 ACK。
    • 时间流逝 5 * RTO,没有新的数据段被发送。
    • 检查 TCPSender 状态,期望状态为 FIN_ACKED,确认连接正常关闭。

多次接收确认号为 isn + 1isn + 8isn + 12 的 ACK,包括重复和过时的 ACK,发送方未产生异常。如何验证?

  1. 多次接收确认号为 isn + 8 的 ACK 时的验证
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1000));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1000));
test.execute(ExpectNoSegment{});

这里先接收了一次确认号为 isn + 8 的 ACK,然后使用 ExpectNoSegment{} 确认没有新的数据段被发送。之后又多次接收确认号为 isn + 8 的 ACK,最后再次使用 ExpectNoSegment{} 确认在这一系列重复 ACK 接收过程中没有异常的数据段发送。

  1. 多次接收确认号为 isn + 1 的 ACK 时的验证
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(1000));
test.execute(ExpectNoSegment{});

这里多次接收确认号为 isn + 1 的过时 ACK,最后使用 ExpectNoSegment{} 确认在接收这些过时 ACK 期间没有异常的数据段发送。

  1. 后续多次接收不同 ACK 时的验证
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 12}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 12}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 12}}.with_win(1000));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(1000));
// 这里虽然没有紧跟 ExpectNoSegment{},但后续通过超时重传等操作的正常执行也侧面验证没有异常

在这一系列多次接收不同确认号(包括重复和过时的)的 ACK 过程中,后续代码里的超时重传等操作正常执行,也从侧面验证了发送方在接收这些 ACK 时没有产生异常。例如后续的超时重传:

test.execute(Tick{5 * rto});
test.execute(ExpectSegment{}.with_payload_size(4).with_data("ijkl").with_seqno(isn + 8).with_fin(true));
test.execute(ExpectNoSegment{});

如果在接收那些 ACK 时发送方产生了异常,这里的超时重传可能就无法正常执行。

综上所述,通过 ExpectNoSegment{} 以及后续操作的正常执行,验证了发送方在接收多次 ACK(包括重复和过时的 ACK)时未产生异常。

关键结论

  • ACK 处理的健壮性:TCPSender 能够正确处理重复的 ACK 和过时的 ACK,不会因为这些异常 ACK 而产生错误的行为,保证了系统的健壮性。
  • 重传机制正常:在超时情况下,发送方能够正常重传未确认的数据段,确保了数据传输的可靠性。
  • 连接关闭正常:即使在接收到各种异常 ACK 的情况下,发送方仍能正确处理连接关闭请求,最终达到 FIN_ACKED 状态,完成连接的正常关闭。

文章作者: 海星来来
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 海星来来 !
  目录