CS144-Lab2

news2024/11/15 6:50:20

实验架构

image-20220322233230399

除了写入传入流之外,TCPReceiver 还负责通知 sender 两件事:

  1. First unassembled” 字节的索引,称为“acknowledgment”或 “ackno”。这是接收方需要来自发送方的第一个字节。
  2. first unassembled ” 索引和“first unacceptable ”索引之间的距离。这称为“window size”。

总的来说,acknowindow size 述了 TCPreceiver 的窗口:TCPsender 被允许发送一系列索引。使用该窗口,TCPreceiver 可以做到流量控制,使发送方限制它发送的数量,直到接收方准备好更多的数据。有时,我们将 ackno 称为窗口的“左边”( TCPRecsigner 的最小索引),而 ackno + window size 则称为“右边缘”(略大于 TCPReceiver 的最大索引)。

在编写 Stream ReassemblerByte Stream 时,您已经完成了实现 TCP Receiver 所涉及的大部分算法工作;本实验是将这些通用类连接到 TCP 的细节。最困难的部分将涉及考虑 TCP 将如何表示每个字节在流中的位置——称为“sequence number”。

我们将要实现的 TCPReceiver 需要完成的功能:

  • 接收 TCP segment
  • 重新组装 ByteStream
  • 发送 acknowindow sizeTCP sender ,以进行流量控制和数据确认

环境配置

当前我们的实验代码位于 master 分支,而在完成 Lab 之前需要合并一些依赖代码,因此执行以下命令:

git merge origin/lab2-startercode

之后重新 make 编译即可。

The TCP Receiver

在 Lab2,我们将实现一个 TCPReceiver,用以接收传入的 TCP segment 并将其转换成用户可读的数据流。

TCPReceiver 除了将读入的数据写入至 ByteStream 中以外,它还需要告诉发送者两个属性

  • 第一个未组装的字节索引,称为确认号ackno,它是接收者需要的第一个字节的索引。
  • 第一个未组装的字节索引第一个不可接受的字节索引之间的距离,称为 窗口长度window size

ackno 和 window size 共同描述了接收者当前的接收窗口。接收窗口是 发送者允许发送数据的一个范围,通常 TCP 接收方使用接收窗口来进行流量控制,限制发送方发送数据。

总的来说,我们将要实现的 TCPReceiver 需要做以下几件事情:

  • 接收TCP segment
  • 重新组装字节流(包括EOF)
  • 确定应该发回给发送者的信号,以进行数据确认和流量控制

1. Translating between 64-bit indexes and 32-bit seqnos

作为热身,我们需要实现TCP表示索引的方式。上周您创建了一个StreamReassembler,它重组子字符串,其中每个字节都有一个64位流索引,流中的第一个字节总是索引为0。64位索引足够大,我们可以将其视为永不溢出。然而,在TCP报头中,空间是宝贵的,流中的每个字节的索引不是用64位的索引表示的,而是用32位的“序列号”或“seqno”表示的。这增加了三个复杂性:

  1. 您的实现需要为32位整数进行规划:TCP中的流可以是任意长的——对于可以通过TCP发送的字节流的长度没有限制。但是232字节只有4GiB,并不是很大。一旦一个32位的序列号计数到232−1,流中的下一个字节的序列号将为0。
  2. TCP序列号从一个随机值开始:为了提高安全性,并避免被属于同一端点之间早期连接的旧段所混淆,TCP试图确保序列号不会被猜测,并且不太可能重复。所以流的序列号不是从0开始的。流中的第一个序列号是一个随机的32位数字,称为初始序列号(Initial sequence number, ISN)。这是表示SYN(流的开始)的序列号。其余的序列号在此之后正常运行:数据的第一个字节将有ISN+1 (mod 232)的序列号,第二个字节将有ISN+2 (mod 232),等等。
  3. 每个逻辑开始和结束占用一个序列号:除了确保接收到所有字节的数据外,TCP还确保可靠地接收流的开始和结束。因此,在TCP中SYN (start -ofstream)和FIN (end- stream)控制标志被分配了序列号。每一个都占用一个序列号。(SYN标志占用的序列号是ISN。)流中的每个数据字节也占用一个序列号。请记住,SYN和FIN不是流本身的一部分,也不是“字节”——它们表示字节流本身的开始和结束。

#ifndef SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH
#define SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH

#include <cstdint>
#include <ostream>

//! \brief A 32-bit integer, expressed relative to an arbitrary initial sequence number (ISN)
//! \note This is used to express TCP sequence numbers (seqno) and acknowledgment numbers (ackno)
class WrappingInt32 {
  private:
    uint32_t _raw_value;  //!< The raw 32-bit stored integer

  public:
    //! Construct from a raw 32-bit unsigned integer
    explicit WrappingInt32(uint32_t raw_value) : _raw_value(raw_value) {}

    uint32_t raw_value() const { return _raw_value; }  //!< Access raw stored value
};

//! Transform a 64-bit absolute sequence number (zero-indexed) into a 32-bit relative sequence number
//! \param n the absolute sequence number
//! \param isn the initial sequence number
//! \returns the relative sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn);

//! Transform a 32-bit relative sequence number into a 64-bit absolute sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute sequence number
//! \returns the absolute sequence number that wraps to `n` and is closest to `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One stream
//! runs from the local TCPSender to the remote TCPReceiver and has one ISN,
//! and the other stream runs from the remote TCPSender to the local TCPReceiver and
//! has a different ISN.
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint);

//! \name Helper functions
//!@{

//! \brief The offset of `a` relative to `b`
//! \param b the starting point
//! \param a the ending point
//! \returns the number of increments needed to get from `b` to `a`,
//! negative if the number of decrements needed is less than or equal to
//! the number of increments
inline int32_t operator-(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() - b.raw_value(); }

//! \brief Whether the two integers are equal.
inline bool operator==(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() == b.raw_value(); }

//! \brief Whether the two integers are not equal.
inline bool operator!=(WrappingInt32 a, WrappingInt32 b) { return !(a == b); }

//! \brief Serializes the wrapping integer, `a`.
inline std::ostream &operator<<(std::ostream &os, WrappingInt32 a) { return os << a.raw_value(); }

//! \brief The point `b` steps past `a`.
inline WrappingInt32 operator+(WrappingInt32 a, uint32_t b) { return WrappingInt32{a.raw_value() + b}; }

//! \brief The point `b` steps before `a`.
inline WrappingInt32 operator-(WrappingInt32 a, uint32_t b) { return a + -b; }
//!@}

#endif  // SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH

这段代码定义了一个名为WrappingInt32的类,表示相对于一个任意初始序列号(ISN)的32位整数。它被用来表示TCP序列号(seqno)和确认号(ackno)。此外,还定义了一些帮助函数,用于转换绝对序列号和相对序列号之间的关系。

具体来说,这里的WrappingInt32类只有一个私有成员变量_raw_value,它表示一个32位无符号整数的原始值。类中定义了一个公有构造函数explicit WrappingInt32(uint32_t raw_value),用于将一个无符号整数转换为WrappingInt32对象。类还提供了一个公有成员函数uint32_t raw_value() const,用于访问对象的原始值。

此外,还定义了两个函数wrapunwrap,分别用于将绝对序列号转换为相对序列号,以及将相对序列号转换为绝对序列号。wrap函数的输入参数为一个64位无符号整数n和一个WrappingInt32对象isn,输出为一个WrappingInt32对象,表示n相对于isn的相对序列号。unwrap函数的输入参数为一个WrappingInt32对象n,一个WrappingInt32对象isn,以及一个64位无符号整数checkpoint,输出为一个64位无符号整数,表示最接近checkpoint并相对于isn的序列号值为n的绝对序列号。

最后,还定义了一些辅助函数,如operator-,用于计算两个WrappingInt32对象之间的差值,operator==operator!=用于比较两个WrappingInt32对象是否相等,operator<<用于将一个WrappingInt32对象输出到流中,以及operator+operator-,分别用于将一个WrappingInt32对象向前或向后移动一定的距离。


//! Transform an "absolute" 64-bit sequence number (zero-indexed) into a WrappingInt32
//! \param n The input absolute 64-bit sequence number
//! \param isn The initial sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
    return isn + n;
}

//! Transform a WrappingInt32 into an "absolute" 64-bit sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute 64-bit sequence number
//! \returns the 64-bit sequence number that wraps to `n` and is closest to `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One stream
//! runs from the local TCPSender to the remote TCPReceiver and has one ISN,
//! and the other stream runs from the remote TCPSender to the local TCPReceiver and
//! has a different ISN.
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
    uint32_t offset = n.raw_value() - wrap(checkpoint, isn).raw_value();
    uint64_t res = checkpoint + offset;
    
    if (offset > (0x80000000) && res >= (0x100000000)) {
        res -= (0x100000000);
    }

    return res;
}

这段代码包含了两个函数的实现,一个是 wrap,另一个是 unwrap

wrap 函数将一个 64 位的绝对序列号转换为一个 32 位的相对序列号,以 isn 为初始序列号。

unwrap 函数将一个 32 位的相对序列号转换为一个 64 位的绝对序列号,以 isn 为初始序列号,并指定一个最近的绝对序列号 checkpoint,函数的返回值是一个绝对序列号,它等于相对序列号 n 对应的绝对序列号,同时又尽可能接近于 checkpoint

其中 wrap 函数的实现比较简单,直接将输入的绝对序列号 n 加上初始序列号 isn,得到一个相对序列号即可。

unwrap 函数的实现稍微复杂一些。首先计算 ncheckpoint 之间的偏移量 offset,即 n 对应的绝对序列号与 checkpoint 的绝对序列号之差。然后将 offset 加到 checkpoint 上,就得到了 n 对应的绝对序列号 res

这里需要注意一个问题,当 offset 的值比较大时(即大于等于 0x80000000),此时 n 对应的序列号可能要“绕一圈”,从而超过了 32 位的表示范围。因此,如果 res 大于等于 0x100000000,则需要减去 0x100000000,即从头开始计数,避免超出 32 位的表示范围。

Implementing the TCP receiver


//! Receives and reassembles segments into a ByteStream, and computes
//! the acknowledgment number and window size to advertise back to the
//! remote TCPSender.
class TCPReceiver {
    //! Our data structure for re-assembling bytes.
    StreamReassembler _reassembler;

    //! The maximum number of bytes we'll store.
    size_t _capacity;

    std::optional<WrappingInt32> _ackno{};
    WrappingInt32 _isn{0};
    WrappingInt32 _seq{0};
    // the index of the last reassembled byte
    uint64_t _checkpt{0};

  public:
    //! \brief Construct a TCP receiver
    //!
    //! \param capacity the maximum number of bytes that the receiver will
    //!                 store in its buffers at any give time.
    TCPReceiver(const size_t capacity) : _reassembler(capacity), _capacity(capacity) {}

    //! \name Accessors to provide feedback to the remote TCPSender
    //!@{

    //! \brief The ackno that should be sent to the peer
    //! \returns empty if no SYN has been received
    //!
    //! This is the beginning of the receiver's window, or in other words, the sequence number
    //! of the first byte in the stream that the receiver hasn't received.
    std::optional<WrappingInt32> ackno() const;

    //! \brief The window size that should be sent to the peer
    //!
    //! Operationally: the capacity minus the number of bytes that the
    //! TCPReceiver is holding in its byte stream (those that have been
    //! reassembled, but not consumed).
    //!
    //! Formally: the difference between (a) the sequence number of
    //! the first byte that falls after the window (and will not be
    //! accepted by the receiver) and (b) the sequence number of the
    //! beginning of the window (the ackno).
    size_t window_size() const;
    //!@}

    //! \brief number of bytes stored but not yet reassembled
    size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); }

    //! \brief handle an inbound segment
    void segment_received(const TCPSegment &seg);

    //! \name "Output" interface for the reader
    //!@{
    ByteStream &stream_out() { return _reassembler.stream_out(); }
    const ByteStream &stream_out() const { return _reassembler.stream_out(); }
    //!@}
};

TCPReceiver 的解释如下:

TCP 接收器用于接收和重新组装 TCP Segment,并计算确认号和窗口大小以返回给远程发送器。

主要成员变量和函数包括:

  • StreamReassembler _reassembler:用于重新组装字节的数据结构;
  • size_t _capacity:TCP 接收器可以同时存储的最大字节数;
  • std::optional<WrappingInt32> _ackno{}:应该发送到对等端的 ackno,如果没有接收到 SYN 则为空;
  • WrappingInt32 _isn{0}:初始序列号(ISN);
  • WrappingInt32 _seq{0}:接收到的最后一个 TCP 分段的序列号;
  • uint64_t _checkpt{0}:最后一个重新组装的字节的索引(绝对序列号);
  • ackno():返回应该发送到对等端的 ackno,如果没有接收到 SYN 则为空;
  • window_size():返回应该发送到对等端的窗口大小;
  • unassembled_bytes():返回已存储但尚未重新组装的字节数;
  • segment_received():处理传入的 TCP 分段。

该类的主要功能是:

  • 接收传入的 TCP 分段,并将其发送到 StreamReassembler 进行重新组装;
  • 计算应该发送给远程 TCPSenderackno 和窗口大小,以便 TCPSender 知道还有多少可用的空间;
  • 提供 stream_out() 函数,用于读取已重新组装的数据。

TCP receiver 在连接生命周期中的状态转移


void TCPReceiver::segment_received(const TCPSegment &seg) {
    /**
     * @note Listen state
     * @def not ackno().has_value() 
     */
    if (!_ackno.has_value()) {
        // Handshake
        if (seg.header().syn) {
            auto rd = get_random_generator();
            _isn = WrappingInt32(rd());
            _seq = seg.header().seqno;

            _reassembler.push_substring(move(seg.payload().copy()), 0, seg.header().fin);
            
            // SYN or FIN make _ackno+1
            auto ctrl = seg.length_in_sequence_space() - seg.payload().size();
            _ackno = WrappingInt32(move(_seq)) + ctrl + _reassembler.first_unassembled();
        }

        return;
    } 

    /**
     * @note FIN_RECV state
     * @def stream_out.input_ended()
     */
    if (_ackno.has_value() && !stream_out().input_ended()) {
        
        /**
         * @note SYN_RECV state
         * @def ackno.has_value() and not stream_out.input_ended()
         * @code 48 - 54
         */
        auto index = unwrap(move(seg.header().seqno), move(_seq + 1), _checkpt);  // "+ 1" for the "SYN"
        
        // data too far, considered out of data
        if (index > _checkpt && ((index - _checkpt) & 0x80000000)) {
            return;
        }
        
        // data too far, considered out of data
        if (index < _checkpt && ((_checkpt - index) & 0x80000000)) {
            return;
        }

        _reassembler.push_substring(move(Buffer(move(seg.payload().copy()))), index, seg.header().fin);
        _ackno = _ackno.value() + _reassembler.first_unassembled() - _checkpt;

        // FIN should make _ackno + 1
        if (stream_out().input_ended()) {
            _ackno = _ackno.value() + 1;
        }

        _checkpt = _reassembler.first_unassembled();
    }
}

这段代码实现了TCP接收端的主要逻辑,即处理TCP段并将它们重新组装成字节流。代码中有几个状态,分别对应TCP协议中的连接建立、连接终止和正常数据传输的不同阶段。

首先,如果接收端还没有收到 SYN,就判断接收到的 TCP 段是否是 SYN,如果是,则在随机生成一个初始序列号后,将接收到的数据传递给 StreamReassembler 进行重新组装,并更新需要发送给发送方的确认序列号 _ackno。如果还没有收到 SYN,则直接返回。

如果接收端已经收到了 SYN,但还没有收到 FIN,则在接收到的TCP段中查找相对于已经接收的字节数偏移量,并使用 StreamReassembler 对接收到的数据进行重新组装。在重新组装之后,需要更新确认序列号 _ackno,并且如果数据流已经结束,需要将确认序列号加一。如果接收到的数据已经过期或已经在之前的数据段中处理过,则不需要重新组装数据,直接返回即可。

如果接收端已经收到了FIN,则不再接受更多的数据,并将 _ackno 加 1 以告诉发送端已经收到了所有数据。

完整代码

  • wrapping_integers.hh
  • wrapping_integers.cc
  • tcp_receiver.hh
  • tcp_receiver.cc

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【项目精选】基于SSH的任务调度系统的设计与实现(视频+源码+论文)

点击下载源码 虽然科技进步在改革开发这几十年来速度飞快&#xff0c;计算机行业也发展迅速&#xff0c;但仍然有大量商家或企业&#xff0c;甚至项目组&#xff0c;采用落后的人工管理方式或者低效的任务调度策略&#xff0c;这无疑是对计算机的一种无视。 计算机处理信息的准…

Python每日一练(20230220)

目录 1. 存在重复元素 II 2. 按要求实现程序功能 3. 分割链表 附录 链表 1. 存在重复元素 II 给定一个整数数组和一个整数 k&#xff0c;判断数组中是否存在两个不同的索引 i 和 j&#xff0c;使得 nums [i] nums [j]&#xff0c;并且 i 和 j 的差的 绝对值 至多为 k。 …

高级数据类型

为了解决单一的业务而存在bitmapsBitmaps类型的基础操作 获取指定key对应偏移量上的bit值getbit key offset 设置指定key对应偏移量上的bit值&#xff0c;value只能是1或0setbit key offset valueBitmaps类型的扩展操作状态位的统计业务需求&#xff1a;1. 统计每天某一部电…

云计算ACP云服务器ECS实例题库(三)

&#x1f618;作者简介&#xff1a;一名99年软件运维应届毕业生&#xff0c;正在自学云计算课程。&#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。&…

关于监控服务器指标、CPU、内存、警报的一些解决方案

文章目录关于监控服务器指标、CPU、内存、警报的一些解决方案Prometheus Grafana 配置 IRIS / Cach 监控服务器Prometheus简介特点架构图Grafana简介特点配置流程自定义Prometheus接口定义配置 Exporter 监控服务器系统资源简介配置流程使用 Alertmanager报警简介配置流程基于…

软考高级-信息系统管理师之知识管理(最新版)

知识管理 知识与知识管理知识管理常用的方法和工具显性知识的管理隐形知识的管理知识管理的工具学习型组织知识产权保护计算机软件保护条例商标法专利法补充建议学的考点:知识与知识管理 1、知识的分类 知识可分为两类,分别是显性知识与隐性知识。 凡是能以文字与数字来表达…

【C++】关联式容器——map和set的使用

文章目录一、关联式容器二、键值对三、树形结构的关联式容器1.set2.multiset3.map4.multimap四、题目练习一、关联式容器 序列式容器&#x1f4d5;:已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这些容器统称为…

新能源汽车,有毒

作者| Mr.K 编辑| Emma来源| 技术领导力(ID&#xff1a;jishulingdaoli)新能源汽车到底有多火&#xff0c;生生逼得奥迪某4S店挂出横幅&#xff1a;我们也有纯电新能源&#xff01;老牌名车的辛酸憋屈溢出屏幕。网友神评补刀“这标语给人‘诺基亚也有智能大屏机’的感觉。”一…

【Java基础】变量

Java基础 变量 variable 变量类型 实例变量(非静态字段) Instance Variables (Non-Static Fields) 类的非静态属性 类变量(静态字段) Class Variables (Static Fields) 类的静态属性 局部变量 Local Variables 参数 Parameters 变量命名 大小写敏感 开头&#xff1a;字…

爬虫基本知识的认知(爬虫流程 HTTP构建)| 爬虫理论课,附赠三体案例

爬虫是指通过程序自动化地从互联网上获取数据的过程。 基本的爬虫流程可以概括为以下几个步骤&#xff1a; 发送 HTTP 请求&#xff1a;通过 HTTP 协议向指定的 URL 发送请求&#xff0c;获取对应的 HTML 页面。解析 HTML 页面&#xff1a;使用 HTML 解析器对获取的 HTML 页面…

linux shell 入门学习笔记4 shell运维和编程语言

shell 运维和编程语言 脚本注释 shell脚本中&#xff0c;#后面的内容表示注释内容&#xff0c;一般是给开发者或使用者观看&#xff0c;解释器会忽略此部分内容注释可以单独写一行&#xff0c;也可以跟在文件末尾保持注释的习惯&#xff0c;尽量使用英文 例子&#xff1a; #…

C++类和对象(2)构造、析构函数

类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 class Date{}; 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员 函数。 默认成员函数&#xff1a;用户没有显式实现&…

什么是健康建筑?

WIKIPEDIA健康建筑是指支援建筑和建筑环境中人们&#xff0c;身体、心理和社会健康与福祉的新兴兴趣领域。建筑物可以成为健康和福祉的关键促进者&#xff0c;因为大多数人大部分时间都花在室内。根据全美国人类活动模式调查&#xff0c;美国人「平均 87% 的时间花在封闭的建筑…

redis-如何保证数据库和缓存双写一致性?

前言 数据库和缓存&#xff08;比如&#xff1a;redis&#xff09;双写数据一致性问题&#xff0c;是一个跟开发语言无关的公共问题。尤其在高并发的场景下&#xff0c;这个问题变得更加严重。 我很负责的告诉大家&#xff0c;该问题无论在面试&#xff0c;还是工作中遇到的概率…

windows版Rsync服务端和客户端cwRsync_4.1.0安装测试

下载地址&#xff1a;https://download.csdn.net/download/qq_32421489/87463506 服务端安装&#xff1a; cwRsyncServer&#xff08;服务端&#xff09;配置步骤 1.双击运行wRsyncServer_4.1.0_Installer.exe。 2.这里创建的账户是操作系统的&#xff0c;创建的这个账户是专…

【 RA4M2开发板环境搭建之串口下载1】

【 RA4M2开发板环境搭建1】1. 前言1.1 活动来源1.2 开发环境1.3 RA4M2开发板2. MDK环境准备2.1 keil 5下载安装2.2 安装RA4M2的软件支持包2.3 Renesas Flash Programmer安装3. RA Smart Configurator配置3.1 下载RA Smart Configurator3.2 安装RA Smart Configurator4. 新建RA4…

你是真的“C”——C语言详解求两个正数最小公倍数的3种境界

C语言详解求两个正数最小公倍数的3种境界~&#x1f60e;前言&#x1f64c;必备小知识~&#x1f618;求最小公倍数境界1~ &#x1f60a;求最小公倍数境界2~ &#x1f60a;求最小公倍数境界3~ &#x1f60a;总结撒花&#x1f49e;博客昵称&#xff1a;博客小梦&#x1f60a; 最喜…

[Incognito 4.0] ictf 2023

一周4赛&#xff0c;有点赶不过来呀。只做了一点&#xff0c;队长组队的时候(每次都中间断掉&#xff0c;一大堆写的都得从头来)CryptoAncient这样的第2次见&#xff0c;第1次就不会&#xff0c;这回看了队友wp终于知道是怎么加密的了Templed每个符号可以表示4位10进制数。原题…

〖产品思维训练白宝书 - 核心竞争力篇⑭〗- 产品经理核心竞争力解读之学习能力

大家好&#xff0c;我是 哈士奇 &#xff0c;一位工作了十年的"技术混子"&#xff0c; 致力于为开发者赋能的UP主, 目前正在运营着 TFS_CLUB社区。 &#x1f4ac; 人生格言&#xff1a;优于别人,并不高贵,真正的高贵应该是优于过去的自己。&#x1f4ac; &#x1f4e…

路径规划 | 图解跳点搜索JPS算法(附ROS C++/Python/Matlab仿真)

目录0 专栏介绍1 A*算法的弊端2 跳点搜索算法2.1 自然与强制邻点2.2 跳点剪枝策略3 算法仿真与实现3.1 算法流程3.2 ROS C实现3.3 Python实现3.4 Matlab实现0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细…