• 【斯坦福计网CS144项目】Lab4: TCPConnection


    在实现了接收端的 TCPReceiver 类和发送端的 TCPSender 类后,本节实验实现掌管这两个类,向用户提供一个完整的 TCP 连接抽象的 TCPConnection 类。正如讲义标题所写,完成本节的实验后,我们是真正攀登上了一个高峰(summit),也是完成了本门课程最重要精华的部分。

    事前提醒,本节的测试量非常大,其中会用你的 TCP 实现与 Linux 的 TCP 实现进行各种条件的交互,之前的 Sender 和 Receiver 类在实现上如果有潜在问题也都会被暴露出来。所以,如果你在尝试自己实现,请做好耐心 Debug 的准备。虽然直接看其它人的 blog 可能感觉逻辑都很自然,但自己动手是完全不一样的。我当时也花了 2 整天左右才通过了所有测试。

    在这里插入图片描述

    TCP 是全双工通信,即双方可以同时发送/接收信息。如图所示,TCPConnection 类应该是架设在 TCPSender 和 TCPReceiver 之上的。当 Sender 需要发送数据包时,从其发送队列 _segments_out 中取出数据包并实际发送出去;当接收到数据包时,做必要处理后交给 Receiver 处理。另外,之前我们一直没有关注 TCP 包头中很重要的一个标识 RST,这也是 TCPConnection 要负责的,即异常状态的处理。最后,TCPConnection 还应该处理好连接的终止,包括正常终止和异常终止两种情况。以下我们分别来讨论这几个问题以及实现思路。

    注:这里发送的实现其实还是将数据包从 Sender 取出转存到 TCPConnection 的一个发送队列中,之后会由更上层的所有者实际负责发送

    接收数据包

    对应函数:void segment_received(const TCPSegment &seg)

    • 如果 RST 标识存在,立刻将出入向的数据流均设置为错误状态,并结束连接。可以将结束连接提炼为一个 _shutdown(bool) 函数,参数代表是正常还是异常结束,这里即为异常结束。否则:
    • 将数据包交给 Receiver 处理,即调用 Receiver 的 segment_received
    • 如果 ACK 标识存在,通知 Sender 其关注的信息(ackno 和 window size),即调用 Sender 的 ack_received
    • 如果数据包有序列号,至少保证有一个回复数据包被回复,即应该检查 Sender 的发送队列是否为空,如果为空则造一个空包。
    • “keep-alive” 包的回复:对方可能发送一个包含无效序列号的数据包以探测连接是否仍有效,此时应回复一个空包。实现方法讲义已给出。

    发送数据包

    • Sender 构造的待发送包还不完整,ackno 和 window size 信息应该由 Receiver 给出(如果存在 ackno,还要设置 ACK 标识)。
    • 任何时刻 Sender 向发送队列中 push 了一个包,TCPConnection 都应该立刻将其发送。因此,TCPConnection 应该实现一个清空 Sender 队列,填充 Receiver 信息并发送的函数(我的实现中命名为 _clear_sendbuf),在任何可能使 Sender 发送数据包的语句之后都调用该函数。

    时间的感知

    与 Sender 相同,TCPConnection 也是通过其 tick 函数被调用而被动的感知时间的经过。当 tick 被调用时:

    • 使 Sender tick
    • 如果 Sender 的重传次数超过某阈值,终止连接并发送一个带 RST 标识的空包。终止连接与上面收到对方 RST 包的处理一样,即调用 _shutdown(false)
    • 如果时机合适,正常结束连接(见下一节)。

    连接的终止

    以上已经讨论过连接异常终止的两种情况,即主动检测到重传次数过多或被动收到对方的 RST 包,这里主要讨论的是连接的正常终止。

    理论上来说,通过不可信连接通信的双方无法绝对确保达成共识(参考:两军问题),不过 TCP 设计了一种可信度较高的方案。以一方(local 方)的观点来看,至少有四件事成立才能正常终止连接:

    • 入向流(inbound)的数据已经完全接收重组完毕并结束。
    • 出向流(outbound)的数据已经完全发送并且带 FIN 标识的包也已被发送。
    • 出向流被对方完全确认接收。
    • local 方相信 remote 方能够满足第三条。这是最难解决的,因为 TCP 不会对 ack 信息做 ack,否则就没有尽头了。于是有两种选项:
      • 延迟关闭(lingering)。当前三条都成立且经过了一定时间后 local 方都没有收到 remote 方重传任何数据,则 local 方能够比较确信双方的所有数据已经正确传输完毕并关闭连接。具体来说,这个时间设置为初始重传超时的 10 倍
      • 被动关闭。如果入向流的结束早于出向流含 FIN 标识包的发送,则 local 方在出向流也结束后立刻关闭连接。其中的原因讲义中给出了解释,这里就不展开了。

    测试

    除了运行自动化测试外,本节完成后已经可以用两个自己的 TCP 端进行通信,以及用自己的 TCP 代替 Lab0 中的 TCPSocket 与真实的服务器进行通信,还可以测试自己 TCP 实现的性能,操作方法讲义中都有描述。


    完整代码链接:
    tcp_connection.hh
    tcp_connection.cc

    通关截图 😎
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    SpringMVC 域对象共享数据
    jq实现多页展示并且进度条轮播
    shell脚本学习记录1(运算符)
    数据结构之队列(Queue)
    BP神经网络的MATLAB实现(含源代码)
    【干货】RPA+AI入门必须知道的39个名词
    注意力机制Attention详解
    Go test之理
    osgEarth示例分析——osgearth_colorfilter
    【Godot】【BUG】4.x NavigationAgent 导航不生效
  • 原文地址:https://blog.csdn.net/Altair_alpha/article/details/125621846