GNU Radio之OFDM Channel Estimation底层C++实现

news2024/11/23 14:57:28

文章目录

  • 前言
  • 一、 OFDM Channel Estimation 模块简介
  • 二、C++ 具体实现
    • 1、初始化和配置参数
    • 2、forecast 函数
    • 3、计算载波偏移量
    • 4、提取信道响应
    • 5、核心的数据处理任务


前言

OFDM Channel Estimation 模块的功能是根据前导码(同步字)估计 OFDM 的信道和粗略频率偏移,本文对 OFDM Channel Estimation 模块的底层 C++ 源码进行剖析。


一、 OFDM Channel Estimation 模块简介

在这里插入图片描述
OFDM Channel Estimation模块的主要目的是从接收的OFDM符号中恢复出发送时的信道条件。主要包括以下功能:

  • 信道估计:
    • 这个模块核心的功能是估计 OFDM 系统中的信道特性。这包括计算信道的频率响应,以便可以对接收到的信号进行适当的校正,以恢复原始发送的数据。信道估计通常利用已知的同步或导频符号来测量信道对这些已知符号的影响。
  • 载波频率偏移估计:
    • 在 OFDM 系统中,载波频率偏移是接收机和发射机之间存在的频率误差。ofdm_chanest_vcvc_impl 类通过分析接收到的 OFDM 符号来估计这一偏移,这对确保数据正确解调是至关重要的。
  • 生成信道抽头(Channel Taps):
    • 信道抽头是描述信道频率响应的复数值,这些复数值可以直接应用于信号解调和均衡过程中。在 OFDM 系统中,每个子载波的信道响应可以被视为一个抽头。
  • 处理和传递元数据:
    • 这个类还负责在GNU Radio的流图中处理和传递相关的元数据,如信道估计结果和载波偏移信息。这些信息通常通过标签(tags)的形式添加到数据流中,供后续的处理块使用。

注意:这个模块只是做估计,未进行均衡,均衡由 OFDM Frame Equalizer 模块实现

二、C++ 具体实现

ofdm_chanest_vcvc_impl 实现了以下关键方法:

  • forecast()
    • 该方法为调度器提供了关于块如何根据输入生成输出的信息。具体来说,它告诉调度器在执行处理之前需要多少输入数据。
  • general_work()
    • 这是块的主要处理函数,它处理输入数据,执行信道估计和载波偏移估计,并生成输出数据。此函数还负责将计算出的信道信息和其他相关元数据标签插入到输出流中。
  • get_carr_offset()get_chan_taps()
    • 这些辅助函数用于计算载波偏移和提取信道抽头,是信道估计过程的核心部分。

1、初始化和配置参数

构造函数 ofdm_chanest_vcvc_impl,实现初始化和配置信道估计的各种参数

// 构造函数,初始化和配置信道估计的各种参数
ofdm_chanest_vcvc_impl::ofdm_chanest_vcvc_impl(	
    const std::vector<gr_complex>& sync_symbol1,	// 同步符号, 用于信道估计
    const std::vector<gr_complex>& sync_symbol2,	// 同步符号, 用于信道估计
    int n_data_symbols,								// 数据符号的数量,表示每次处理的数据符号数
    int eq_noise_red_len,							// 均衡噪声减少的长度,用于设置信道估计中的一些内部处理
    int max_carr_offset,							// 最大载波偏移,用于粗略频率估计
    bool force_one_sync_symbol)						// 用于控制是否强制只使用一个同步符号进行信道估计
    : block("ofdm_chanest_vcvc",
            io_signature::make(1, 1, sizeof(gr_complex) * sync_symbol1.size()),		// 示这个模块有一个输入端口,每个输入项是一个复数向量,向量的长度等于 sync_symbol1.size()。
            io_signature::make(1, 2, sizeof(gr_complex) * sync_symbol1.size())),	// 表示这个模块有一个或两个输出端口,输出数据格式与输入相同。
      d_fft_len(sync_symbol1.size()),	// FFT的长度
      d_n_data_syms(n_data_symbols),
      d_n_sync_syms(1),
      d_eq_noise_red_len(eq_noise_red_len),
      d_ref_sym((!sync_symbol2.empty() && !force_one_sync_symbol) ? sync_symbol2	// 参考同步符号为 sync_symbol2
                                                                  : sync_symbol1),
      d_corr_v(sync_symbol2),	// 用于信道估计的向量,用于存储相关性向量
	  // 用于存储已知和新的符号差异。
	  d_known_symbol_diffs(0, 0),	
      d_new_symbol_diffs(0, 0),	
      d_first_active_carrier(0),	// 第一个活跃子载波的索引  
      d_last_active_carrier(sync_symbol2.size() - 1),	// 最后一个活跃子载波的索引
      d_interpolate(false)			// 不需要插值
{
    // Set index of first and last active carrier
	// ******************************寻找活跃载波**********************
	/*
		这两个循环用于确定第二个同步字中第一个和最后一个非零(即活跃)载波的索引。
		这是为了确定数据中的有效范围。
	*/
    for (int i = 0; i < d_fft_len; i++) {
        if (d_ref_sym[i] != gr_complex(0, 0)) {
            d_first_active_carrier = i;
            break;
        }
    }
    for (int i = d_fft_len - 1; i >= 0; i--) {
        if (d_ref_sym[i] != gr_complex(0, 0)) {
            d_last_active_carrier = i;
            break;
        }
    }

    // Sanity checks
    // ******************************合理性检查**********************
	/*
		这部分代码首先检查两个同步符号的长度是否相等,如果不等则抛出异常。
		接着,根据是否强制使用一个同步符号来调整同步符号的数量。
		如果只有一个同步符号且下一个载波是零,则开启插值模式。
	*/
	if (!sync_symbol2.empty()) {
        if (sync_symbol1.size() != sync_symbol2.size()) {
            throw std::invalid_argument("sync symbols must have equal length.");
        }
        if (!force_one_sync_symbol) {
            d_n_sync_syms = 2;
        }
    } else {
        if (sync_symbol1[d_first_active_carrier + 1] == gr_complex(0, 0)) {
            d_last_active_carrier++;
            d_interpolate = true;
        }
    }

    // Set up coarse freq estimation info
    // Allow all possible values:
    // ******************************设置频率估计参数**********************
	/*
		这部分设置最大负载和正载波偏移量,并确保这些偏移量为偶数,这是因为同步算法要求。
	*/
	d_max_neg_carr_offset = -d_first_active_carrier;	// 系统可以容忍的最大向下(或向负方向)的频率偏移量,表示为载波数量。负载波偏移意味着接收频率低于发射频率。
    d_max_pos_carr_offset = d_fft_len - d_last_active_carrier - 1;	// 系统可以容忍的最大向上(或向正方向)的频率偏移量,同样表示为载波数量。正载波偏移意味着接收频率高于发射频率。
    if (max_carr_offset != -1) {
        d_max_neg_carr_offset = std::max(-max_carr_offset, d_max_neg_carr_offset);
        d_max_pos_carr_offset = std::min(max_carr_offset, d_max_pos_carr_offset);
    }
    // Carrier offsets must be even
    if (d_max_neg_carr_offset % 2)
        d_max_neg_carr_offset++;
    if (d_max_pos_carr_offset % 2)
        d_max_pos_carr_offset--;

	// ******************************处理相关性向量**********************
	/*
		如果使用两个同步符号,计算每个载波的相关性。如果只使用一个,
		重新设置相关向量并计算已知符号之间的差异。
	*/
	if (d_n_sync_syms == 2) {
        for (int i = 0; i < d_fft_len; i++) {
            if (sync_symbol1[i] == gr_complex(0, 0)) {
                d_corr_v[i] = gr_complex(0, 0);
            } else {
                d_corr_v[i] /= sync_symbol1[i];		// 同步字2 ÷ 同步字1
            }
        }
    } else {
        d_corr_v.resize(0, 0);
        d_known_symbol_diffs.resize(d_fft_len, 0);
        d_new_symbol_diffs.resize(d_fft_len, 0);
        for (int i = d_first_active_carrier;
             i < d_last_active_carrier - 2 && i < d_fft_len - 2;
             i += 2) {
            d_known_symbol_diffs[i] = std::norm(sync_symbol1[i] - sync_symbol1[i + 2]);
        }
    }

	// ******************************设置输出和速率**********************
    set_output_multiple(d_n_data_syms);		// 设置输出的数量
    set_relative_rate((uint64_t)d_n_data_syms, (uint64_t)(d_n_data_syms + d_n_sync_syms)); // 设置输出的相对速率
    set_tag_propagation_policy(TPP_DONT);	// 设置输出的标签传播策略
}

2、forecast 函数

forecast 函数是由框架在调度块(block)执行之前调用的。这个函数的主要作用是告诉调度器(scheduler),在实际调用处理函数(如 general_work 或 work 函数)之前,块(block)需要多少输入项(samples)来产生预期的输出项。这一机制确保在执行处理函数时,块有足够的数据来进行处理,从而避免处理函数中出现缓冲区下溢的情况。

// forecast 方法在 GNU Radio 中的用途是为调度器提供关于数据依赖关系的信息,
// 即它告诉系统在产生一定数量的输出之前,需要多少输入。这个方法对于确保块在
// 有足够的输入数据处理之前不被调用是非常重要的。
void ofdm_chanest_vcvc_impl::forecast(int noutput_items,		// 预期的输出项数。在这个上下文中,它指的是调度器计划产生的输出数据块的数量
                                      gr_vector_int& ninput_items_required)	// 用于存储每个输入流所需的输入项数
{	
	// ************************逻辑解释************************
	// 这个 forecast 方法实现的基本思想是:为了产生 noutput_items 个输出,每个输出都需要 d_n_data_syms 个数据符号,但每组输入还包括一定数量的同步符号 (d_n_sync_syms)。
	// 因此,我们需要从输入流中获取足够的数据来覆盖这两部分的需求。
	// 这种计算方式确保了无论何时调度器决定调用这个块处理数据时,块都能有足够的输入数据来满足其输出产量的需求,从而避免在数据不足时处理数据,这是确保数据流正确性的关键一环。


	// 计算并设置第一个输入流(索引为0)所需的输入项数
	// (noutput_items / d_n_data_syms): 将预期的输出项数除以每组数据符号的数量,这个操作基本上在计算为了生成所需的输出数量,需要处理多少组数据。
	// (d_n_data_syms + d_n_sync_syms): 计算得到的每组数据的数量乘以每组中数据符号和同步符号的总和
	ninput_items_required[0] =
        (noutput_items / d_n_data_syms) * (d_n_data_syms + d_n_sync_syms);	
}

3、计算载波偏移量

// 用于计算并返回载波偏移量
int ofdm_chanest_vcvc_impl::get_carr_offset(const gr_complex* sync_sym1,	// 同步符号,用于计算载波偏移
                                            const gr_complex* sync_sym2)
{
    int carr_offset = 0;
    if (!d_corr_v.empty()) {
        // Use Schmidl & Cox method
        // 相关性的估计方法,如Schmidl & Cox方法
        float Bg_max = 0;	// 初始化最大相关性度量为0
        // g here is 2g in the paper
        // 从最大负载波偏移量到最大正载波偏移量遍历,步长为2
        for (int g = d_max_neg_carr_offset; g <= d_max_pos_carr_offset; g += 2) {
			// 初始化一个临时复数用于计算当前偏移量 g 下的相关性
			gr_complex tmp = gr_complex(0, 0);
			// 对每个FFT长度内的点,如果相关向量在该点不为零,则计算该点在两个同步符号上的相关性,并累加到 tmp。
            for (int k = 0; k < d_fft_len; k++) {
                if (d_corr_v[k] != gr_complex(0, 0)) {
                    tmp += std::conj(sync_sym1[k + g]) * std::conj(d_corr_v[k]) *
                           sync_sym2[k + g];
                }
            }
			// 如果当前的 tmp 的绝对值大于已知的最大值,则更新最大值和对应的载波偏移量。
            if (std::abs(tmp) > Bg_max) {
                Bg_max = std::abs(tmp);
                carr_offset = g;
            }
        }
    } else {
        // Correlate
        std::fill(d_new_symbol_diffs.begin(), d_new_symbol_diffs.end(), 0);
        for (int i = 0; i < d_fft_len - 2; i++) {
            d_new_symbol_diffs[i] = std::norm(sync_sym1[i] - sync_sym1[i + 2]);
        }

        float sum;
        float max = 0;
        for (int g = d_max_neg_carr_offset; g <= d_max_pos_carr_offset; g += 2) {
            sum = 0;
            for (int j = 0; j < d_fft_len; j++) {
                if (d_known_symbol_diffs[j]) {
                    sum += (d_known_symbol_diffs[j] * d_new_symbol_diffs[j + g]);
                }
                if (sum > max) {
                    max = sum;
                    carr_offset = g;
                }
            }
        }
    }
    return carr_offset;
}

他这里的理论参考的是 Robust Frequency and Timing Synchronization for OFDM. Timothy M. Schmidl and Donald C. Cox, Fellow, IEEE 的论文内容。

4、提取信道响应

// 用于从同步符号中提取信道响应,即“信道抽头”(channel taps)。这些信道抽头代表了在多径环境下,信道对每个频率的响应。
void ofdm_chanest_vcvc_impl::get_chan_taps(const gr_complex* sync_sym1,
                                           const gr_complex* sync_sym2,
                                           int carr_offset,					// 载波偏移
                                           std::vector<gr_complex>& taps)	// 用于存储计算出的信道抽头
{
	// ***************选择使用的同步符号****************
    const gr_complex* sym = ((d_n_sync_syms == 2) ? sync_sym2 : sync_sym1);	// 使用 sync_sym2 同步符号数组
	// ***************初始化信道抽头向量****************
	std::fill(taps.begin(), taps.end(), gr_complex(0, 0));
	// ***************设置循环边界****************
	/*
		初始化循环的起始和结束索引。根据载波偏移调整这些索引,
		以避免数组越界。载波偏移向正方向时,从偏移处开始;
		向负方向时,结束点前移。
	*/
    int loop_start = 0;
    int loop_end = d_fft_len;
    if (carr_offset > 0) {
        loop_start = carr_offset;
    } else if (carr_offset < 0) {
        loop_end = d_fft_len + carr_offset;
    }

	// ***************计算信道抽头****************
	/*
		遍历有效的FFT点范围。只有当参考符号在相应的位置不为零时,才计算信道抽头,避免除零错误。
		信道抽头是通过将当前同步符号(经过信道后)除以参考同步符号得到的。
	*/
    for (int i = loop_start; i < loop_end; i++) {
        if ((d_ref_sym[i - carr_offset] != gr_complex(0, 0))) {
            taps[i - carr_offset] = sym[i] / d_ref_sym[i - carr_offset];
        }
    }

	// ***************插值处理****************
	/*
		如果启用了插值 (d_interpolate),对信道抽头进行插值处理,填补那些没有直接计算的点。
		这通常用于提高信道估计的平滑性和准确性。
	*/
    if (d_interpolate) {
        for (int i = d_first_active_carrier + 1; i < d_last_active_carrier; i += 2) {
            taps[i] = taps[i - 1];
        }
        taps[d_last_active_carrier] = taps[d_last_active_carrier - 1];
    }

	// ***************噪声降低处理(未实现)****************
    if (d_eq_noise_red_len) {
        // TODO
        // 1) IFFT
        // 2) Set all elements > d_eq_noise_red_len to zero
        // 3) FFT
    }
}

5、核心的数据处理任务

int ofdm_chanest_vcvc_impl::general_work(int noutput_items,				// 函数打算产生的输出项数
                                         gr_vector_int& ninput_items,	// 每个输入流的输入项数
                                         gr_vector_const_void_star& input_items,
                                         gr_vector_void_star& output_items)
{
    const gr_complex* in = (const gr_complex*)input_items[0];
    gr_complex* out = (gr_complex*)output_items[0];
    const int framesize = d_n_sync_syms + d_n_data_syms;	// 定义处理的总帧大小,包括同步符号和数据符号的数量。

    // Channel info estimation
    // 信道信息估计
    int carr_offset = get_carr_offset(in, in + d_fft_len);		// 计算载波偏移量
    std::vector<gr_complex> chan_taps(d_fft_len, 0);			// 存储信道抽头
    get_chan_taps(in, in + d_fft_len, carr_offset, chan_taps);	// 填充chan_taps向量
	// 在输出流的特定位置添加标签,标识载波偏移和信道抽头的信息。
	add_item_tag(0,
                 nitems_written(0),
                 pmt::string_to_symbol("ofdm_sync_carr_offset"),
                 pmt::from_long(carr_offset));
    add_item_tag(0,
                 nitems_written(0),
                 pmt::string_to_symbol("ofdm_sync_chan_taps"),
                 pmt::init_c32vector(d_fft_len, chan_taps));

    // Copy data symbols
    // 复制数据符号到输出
    if (output_items.size() == 2) {	// 如果输出项数为2,则将chan_taps数据复制到第二个输出。
        gr_complex* out_chantaps = ((gr_complex*)output_items[1]);
        memcpy((void*)out_chantaps, (void*)&chan_taps[0], sizeof(gr_complex) * d_fft_len);
        produce(1, 1);
    }
	// 将输入中的数据符号部分复制到输出
    memcpy((void*)out,
           (void*)&in[d_n_sync_syms * d_fft_len],
           sizeof(gr_complex) * d_fft_len * d_n_data_syms);

    // Propagate tags
    // 传递标签
    /*
		从输入流获取所有标签并调整它们的位置,考虑到同步符号的存在。
	*/
    std::vector<gr::tag_t> tags;
    get_tags_in_range(tags, 0, nitems_read(0), nitems_read(0) + framesize);
    for (unsigned t = 0; t < tags.size(); t++) {
        int offset = tags[t].offset - nitems_read(0);
        if (offset < d_n_sync_syms) {
            offset = 0;
        } else {
            offset -= d_n_sync_syms;
        }
        tags[t].offset = offset + nitems_written(0);
        add_item_tag(0, tags[t]);
    }

	// 生成和消耗数据
	// 指示产生的输出项数和消耗的输入项数。
    produce(0, d_n_data_syms);
    consume_each(framesize);
    return WORK_CALLED_PRODUCE;
}

我的qq:2442391036,欢迎交流!


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

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

相关文章

Java:SpringBoot如何优化启动速度

一、yml中设置懒加载 spring:main:lazy-initialization: true 二、SpringBoot启动类中添加注解 Indexed &#xff08;Spring5才有该注解&#xff09; Indexed EnableAsync RestController SpringBootApplication(exclude {WxMaAutoConfiguration.class}) EnableTransactionM…

web服务的部署及高级优化

搭建web服务器 1.1、配置主机IP以及软件仓库搭建 [rootserver129 ~]# vmset.sh 100 //主机IP配置为172.25.254.100 1.2、查看搭建web服务器所需的软件包 [rootserver100 ~]# dnf search nginx 名称 精准匹配&#xff1a;nginx nginx.x86_64 : A high performance web serve…

31.基础乐理-首调与固定调

首调与固定调的概念&#xff1a; 首调 与 固定调 这两个词都是针对 唱名 来说的&#xff0c;针对唱名1234567 来说的&#xff0c;和别的没什么关系&#xff0c;这两个概念是唱名的两种不同表达方式 首调&#xff1a;虽然各个大调实际使用的按键、使用的音名都是不一样的&#x…

【4103】基于小程序实现的老年人健康管理平台

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

学习C语言的指针

有一阵没更新了&#xff0c;因为最近比较繁忙&#xff0c;所以更新比较慢&#xff0c;还在慢慢学习 话不多说&#xff0c;开始今天的内容&#xff0c;聊一聊C语言指针。 很多小伙伴可能会被指针这个名字吓到&#xff0c;觉得很难&#xff0c;实际上确实有点难&#xff0c;但是…

算力云平台

先预热下 目标&#xff1a; 算力互联&#xff0c;随需随取&#xff1b;让算力化零为整&#xff0c;化整为零 场景&#xff1a; 1. 个人pc改造&#xff0c;个人算力出租&#xff0c;类似之前的jiluyou模式 2. 服务器中间商准系统集成&#xff0c;目前了解到挖矿不好弄了以后…

MVC架构简述

MVC简介 MVC 是一种非常常见且常用的分层架构&#xff0c;主要包括&#xff1b;M - mode 对象层&#xff0c;封装到 domain 里。V - view 展示层&#xff0c;但因为目前都是前后端分离的项目&#xff0c;几乎不会在后端项目里写 JSP 文件了。C - Controller 控制层&#xff0c…

qt安装历史版本5.15.2

0 背景 因为需要&#xff0c;所以需要安装qt5的最后一个版本qt5.15.2&#xff0c;但是下载qt安装器后&#xff0c;发现没有想要的版本。后面才发现&#xff0c;可以筛选历史版本进行安装。 1 解决 1&#xff0c;打开qt安装程序&#xff0c;勾选Archive后&#xff0c;点击筛选…

【简洁易学】TypeScript 学习笔记

文章目录 TypeScript学习笔记一、TS简介1. 学习前提2. TypeScript是什么&#xff1f;3. TypeScript增加了什么&#xff1f; 二、TS开发环境搭建1. 下载、安装Node.js2. npm安装TypeScript3. 创建一个TS文件&#xff0c;使用tsc对TS文件进行编译 三、TS的类型1. 类型声明2. 类型…

技术不爱听:可视化大屏难点不在于制作过程,而是做成什么样

制作可视化大屏的是一个技术活&#xff0c;需要掌握一定的前端技术和数据可视化技术&#xff0c;例如HTML、CSS、JavaScript、Canvas、SVG、three.js、甚至还涉及到建模、BIM、GIS等等。 但是&#xff0c;仅仅掌握这些技术并不足以制作出一个好的可视化大屏&#xff0c;更重要的…

js逆向进阶篇-某团酒店

提示!本文章仅供学习交流,严禁用于任何商业和非法用途,未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,如有侵权,可联系本文作者删除! 案例分析: 先来看看请求中有哪些参数是需要我们逆向,如下: mtgsig、fp、roh…

数据结构与算法-抽象数据类型ADT系列

以前在学习数据结构的时候做实验&#xff0c;老师要求用ADT抽象数据类型来写这些实现代码。后面也要复习数据结构&#xff0c;在这里就先放下链接。不过以前学习的时候使用的编程语言是C&#xff0c;里面会用到很多指针。现在编代码过程大多数时候都是用Java。不过思路应该还是…

C# WinForm —— 09 标签、文本框、按钮控件

标签 Label 一般显示不能编辑的文本或图像 常用属性、事件&#xff1a; 属性用途(Name)标签对象的ID&#xff0c;在代码里引用标签的时候会用到,一般以 lbl 开头Text设置或获取 界面上显示的 文本信息Image显示图像ImageList图像集&#xff0c;通常和 ListView ToolStrip Tre…

MS17-010---利用“永恒之蓝”漏洞攻击 win7主机

免责声明:本文仅做技术交流与学习.... 目录 一.前置知识 1.何为永恒之蓝&#xff1f; 2.什么是SMB协议&#xff1f; 3.SMB工作原理是什么&#xff1f; 二、实验环境 三、实验步骤 nmap扫描 msf一把梭哈 shell执行命令 远程连接 一&#xff0e; 二&#xff0e; 一.前…

C++常用的输入输出方法(ACM模式)

文章目录 前言一、输入输出方法1、cin2、getline()3、getchar() 二、算法案例1、一维数组1.1 输入固定长度1.2长度不固定 2、固定二维数组3、以非空格隔开的元素输入3、常见数据结构定义以及输入3.1 链表 前言 C中的输入输出函数有很多&#xff0c;我们本章只针对大部分算法题…

vue +antvX6 根据节点与线,动态设置节点坐标生成流程图

需求 vue2 antvX6完成流程图&#xff0c;但只有节点与线&#xff0c;没有节点的坐标&#xff0c;需要根据节点的顺序显示流程图。 需求&#xff1a; 1.根据数据动态生成对应的节点与线&#xff1b; 2.节点不能重叠&#xff1b; 3.节点与线可拖拽&#xff1b; 4.因为线存在重…

渗透之sql注入联合查询的注入

sql注入产生的原因&#xff1a; 由于程序过滤不严谨&#xff0c;导致用户有一些异常输入&#xff0c;最终触发数据库的查询。所以会出现sql注入这个问题。有些恶意的人就会利用这些信息导致数据库泄露。 注意&#xff1a;一般我们存在注入点我们会查询管理员的账号和密码&#…

【C 数据结构】深度优先搜索、广度优先搜索

文章目录 【 1. DFS 深度优先搜索 】1.1 基本原理1.2 C 实现 【 2. BFS 广度优先搜索 】2.1 基本原理2.2 C 实现 【 3. 深度优先生成树、广度优先生成树 】【 4. 深度优先生成森林、广度优先生成森林 】4.1 深度优先生成森林4.2 广度优先生成森林 对存储的图中的顶点进行遍历搜…

P6技巧-关于汇总项目Summarize的使用

前言 不知你在使用P6项目时是否察觉到这么一个有趣的现象&#xff0c;在打开一个项目&#xff08;展开详细任务&#xff09;时&#xff0c;在项目页签下可以看到该项目能反馈此时项目的总体进展及完成时间等内容&#xff0c;而当项目关闭时&#xff0c;其前面所展示的进展信息…

2.1.5 EtherChannel 简介

1、EtherChannel 简介 EtherChannel &#xff08;以太网通道&#xff09;是由 Cisco 公司开发的、应用于交换机之间的多链路捆绑技术。它的基本原理是将两台设备间多条以太网链路捆绑在一起组成一条逻辑链路&#xff0c;形成一个端口通道&#xff08;PortChannel&#xff09;&a…